1use crate::{CancunPayloadFields, ExecutionPayloadSidecar, PayloadError, PraguePayloadFields};
4use alloc::{
5 string::{String, ToString},
6 vec::Vec,
7};
8use alloy_consensus::{
9 constants::MAXIMUM_EXTRA_DATA_SIZE, Blob, Block, BlockBody, BlockHeader, Bytes48, Header,
10 HeaderInfo, Transaction, EMPTY_OMMER_ROOT_HASH,
11};
12#[cfg(feature = "kzg")]
13use alloy_eips::eip4844::{AsAlloy, AsCkzg};
14use alloy_eips::{
15 calc_next_block_base_fee,
16 eip1559::BaseFeeParams,
17 eip2718::{Decodable2718, Eip2718Result, Encodable2718, WithEncoded},
18 eip4844::BlobTransactionSidecar,
19 eip4895::{Withdrawal, Withdrawals},
20 eip7594::{BlobTransactionSidecarEip7594, CELLS_PER_EXT_BLOB},
21 eip7685::Requests,
22 eip7840::BlobParams,
23 eip7928::EMPTY_BLOCK_ACCESS_LIST_HASH,
24 BlockNumHash,
25};
26use alloy_primitives::{keccak256, Address, Bloom, Bytes, Sealable, Sealed, B256, B64, U256};
27use core::iter::{FromIterator, IntoIterator};
28
29pub type ExecutionPayloadBodiesV1 = Vec<Option<ExecutionPayloadBodyV1>>;
31
32pub type ExecutionPayloadBodiesV2 = Vec<Option<ExecutionPayloadBodyV2>>;
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
41pub struct PayloadId(pub B64);
42
43#[cfg(feature = "ssz")]
44impl ssz::Encode for PayloadId {
45 fn is_ssz_fixed_len() -> bool {
46 <B64 as ssz::Encode>::is_ssz_fixed_len()
47 }
48
49 fn ssz_fixed_len() -> usize {
50 <B64 as ssz::Encode>::ssz_fixed_len()
51 }
52
53 fn ssz_bytes_len(&self) -> usize {
54 ssz::Encode::ssz_bytes_len(&self.0)
55 }
56
57 fn ssz_append(&self, buf: &mut Vec<u8>) {
58 ssz::Encode::ssz_append(&self.0, buf);
59 }
60}
61
62#[cfg(feature = "ssz")]
63impl ssz::Decode for PayloadId {
64 fn is_ssz_fixed_len() -> bool {
65 <B64 as ssz::Decode>::is_ssz_fixed_len()
66 }
67
68 fn ssz_fixed_len() -> usize {
69 <B64 as ssz::Decode>::ssz_fixed_len()
70 }
71
72 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
73 <B64 as ssz::Decode>::from_ssz_bytes(bytes).map(Self)
74 }
75}
76
77impl PayloadId {
80 pub fn new(id: [u8; 8]) -> Self {
82 Self(B64::from(id))
83 }
84}
85
86impl core::fmt::Display for PayloadId {
87 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
88 self.0.fmt(f)
89 }
90}
91
92impl core::str::FromStr for PayloadId {
93 type Err = <B64 as core::str::FromStr>::Err;
94
95 fn from_str(s: &str) -> Result<Self, Self::Err> {
96 s.parse().map(Self)
97 }
98}
99
100impl From<B64> for PayloadId {
101 fn from(value: B64) -> Self {
102 Self(value)
103 }
104}
105
106#[derive(Clone, Debug, Default, PartialEq, Eq)]
108#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
109#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
110#[non_exhaustive]
111pub struct PayloadExtras {
112 pub bal: Option<Bytes>,
114}
115
116impl From<Option<Bytes>> for PayloadExtras {
117 fn from(bal: Option<Bytes>) -> Self {
118 Self { bal }
119 }
120}
121
122impl From<Bytes> for PayloadExtras {
123 fn from(bal: Bytes) -> Self {
124 Self { bal: Some(bal) }
125 }
126}
127
128#[derive(Clone, Debug, PartialEq, Eq)]
140#[cfg_attr(feature = "serde", derive(serde::Serialize))]
141#[cfg_attr(feature = "serde", serde(untagged))]
142#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
143pub enum ExecutionPayloadFieldV2 {
144 V1(ExecutionPayloadV1),
146 V2(ExecutionPayloadV2),
148}
149
150#[cfg(feature = "serde")]
153impl<'de> serde::Deserialize<'de> for ExecutionPayloadFieldV2 {
154 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
155 where
156 D: serde::Deserializer<'de>,
157 {
158 #[derive(serde::Deserialize)]
159 struct Helper {
160 #[serde(flatten)]
161 payload_inner: ExecutionPayloadV1,
162 withdrawals: Option<Vec<Withdrawal>>,
163 }
164
165 let helper = Helper::deserialize(deserializer)?;
166 Ok(match helper.withdrawals {
167 Some(withdrawals) => {
168 Self::V2(ExecutionPayloadV2 { payload_inner: helper.payload_inner, withdrawals })
169 }
170 None => Self::V1(helper.payload_inner),
171 })
172 }
173}
174
175impl ExecutionPayloadFieldV2 {
176 pub fn from_block_slow<T, H>(block: &Block<T, H>) -> Self
186 where
187 T: Encodable2718,
188 H: BlockHeader + Sealable,
189 {
190 Self::from_block_unchecked(block.hash_slow(), block)
191 }
192
193 pub fn from_block_unchecked<T, H>(block_hash: B256, block: &Block<T, H>) -> Self
202 where
203 T: Encodable2718,
204 H: BlockHeader,
205 {
206 if block.body.withdrawals.is_some() {
207 Self::V2(ExecutionPayloadV2::from_block_unchecked(block_hash, block))
208 } else {
209 Self::V1(ExecutionPayloadV1::from_block_unchecked(block_hash, block))
210 }
211 }
212
213 pub fn into_v1_payload(self) -> ExecutionPayloadV1 {
215 match self {
216 Self::V1(payload) => payload,
217 Self::V2(payload) => payload.payload_inner,
218 }
219 }
220
221 pub fn into_payload(self) -> ExecutionPayload {
223 match self {
224 Self::V1(payload) => ExecutionPayload::V1(payload),
225 Self::V2(payload) => ExecutionPayload::V2(payload),
226 }
227 }
228}
229
230#[cfg(feature = "ssz")]
231impl ssz::Encode for ExecutionPayloadFieldV2 {
232 fn is_ssz_fixed_len() -> bool {
233 false
234 }
235
236 fn ssz_append(&self, buf: &mut Vec<u8>) {
237 match self {
238 Self::V1(payload) => payload.ssz_append(buf),
239 Self::V2(payload) => payload.ssz_append(buf),
240 }
241 }
242
243 fn ssz_bytes_len(&self) -> usize {
244 match self {
245 Self::V1(payload) => payload.ssz_bytes_len(),
246 Self::V2(payload) => payload.ssz_bytes_len(),
247 }
248 }
249}
250
251#[cfg(feature = "ssz")]
252impl ssz::Decode for ExecutionPayloadFieldV2 {
253 fn is_ssz_fixed_len() -> bool {
254 false
255 }
256
257 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
258 match <ExecutionPayloadV2 as ssz::Decode>::from_ssz_bytes(bytes) {
259 Ok(payload) => Ok(Self::V2(payload)),
260 Err(_) => <ExecutionPayloadV1 as ssz::Decode>::from_ssz_bytes(bytes).map(Self::V1),
261 }
262 }
263}
264
265#[derive(Clone, Debug, PartialEq, Eq)]
267#[cfg_attr(feature = "serde", derive(serde::Serialize))]
268#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
269#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode))]
270#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
271pub struct ExecutionPayloadInputV2 {
272 #[cfg_attr(feature = "serde", serde(flatten))]
274 pub execution_payload: ExecutionPayloadV1,
275 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
277 pub withdrawals: Option<Vec<Withdrawal>>,
278}
279
280#[cfg(feature = "serde")]
281impl<'de> serde::Deserialize<'de> for ExecutionPayloadInputV2 {
282 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
283 where
284 D: serde::Deserializer<'de>,
285 {
286 #[derive(serde::Deserialize)]
287 #[serde(rename_all = "camelCase", deny_unknown_fields)]
288 struct Helper {
289 parent_hash: B256,
290 fee_recipient: Address,
291 state_root: B256,
292 receipts_root: B256,
293 logs_bloom: Bloom,
294 prev_randao: B256,
295 #[serde(with = "alloy_serde::quantity")]
296 block_number: u64,
297 #[serde(with = "alloy_serde::quantity")]
298 gas_limit: u64,
299 #[serde(with = "alloy_serde::quantity")]
300 gas_used: u64,
301 #[serde(with = "alloy_serde::quantity")]
302 timestamp: u64,
303 extra_data: Bytes,
304 base_fee_per_gas: U256,
305 block_hash: B256,
306 transactions: Vec<Bytes>,
307 withdrawals: Option<Vec<Withdrawal>>,
308 }
309
310 let helper = Helper::deserialize(deserializer)?;
311 Ok(Self {
312 execution_payload: ExecutionPayloadV1 {
313 parent_hash: helper.parent_hash,
314 fee_recipient: helper.fee_recipient,
315 state_root: helper.state_root,
316 receipts_root: helper.receipts_root,
317 logs_bloom: helper.logs_bloom,
318 prev_randao: helper.prev_randao,
319 block_number: helper.block_number,
320 gas_limit: helper.gas_limit,
321 gas_used: helper.gas_used,
322 timestamp: helper.timestamp,
323 extra_data: helper.extra_data,
324 base_fee_per_gas: helper.base_fee_per_gas,
325 block_hash: helper.block_hash,
326 transactions: helper.transactions,
327 },
328 withdrawals: helper.withdrawals,
329 })
330 }
331}
332
333impl ExecutionPayloadInputV2 {
334 pub fn into_payload(self) -> ExecutionPayload {
336 match self.withdrawals {
337 Some(withdrawals) => ExecutionPayload::V2(ExecutionPayloadV2 {
338 payload_inner: self.execution_payload,
339 withdrawals,
340 }),
341 None => ExecutionPayload::V1(self.execution_payload),
342 }
343 }
344}
345
346impl From<ExecutionPayloadInputV2> for ExecutionPayload {
347 fn from(input: ExecutionPayloadInputV2) -> Self {
348 input.into_payload()
349 }
350}
351
352#[derive(Clone, Debug, PartialEq, Eq)]
358#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
359#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
360#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
361#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
362pub struct ExecutionPayloadEnvelopeV2 {
363 pub execution_payload: ExecutionPayloadFieldV2,
371 pub block_value: U256,
373}
374
375impl ExecutionPayloadEnvelopeV2 {
376 pub fn into_v1_payload(self) -> ExecutionPayloadV1 {
378 self.execution_payload.into_v1_payload()
379 }
380}
381
382#[derive(Clone, Debug, PartialEq, Eq)]
388#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
389#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
390#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
391#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
392pub struct ExecutionPayloadEnvelopeV3 {
393 pub execution_payload: ExecutionPayloadV3,
395 pub block_value: U256,
397 pub blobs_bundle: BlobsBundleV1,
399 pub should_override_builder: bool,
402}
403
404#[derive(Clone, Debug, PartialEq, Eq, derive_more::Deref, derive_more::DerefMut)]
410#[cfg_attr(feature = "serde", derive(serde::Serialize))]
411#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
412#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
413pub struct ExecutionPayloadEnvelopeV4 {
414 #[deref]
416 #[deref_mut]
417 #[cfg_attr(feature = "serde", serde(flatten))]
418 pub envelope_inner: ExecutionPayloadEnvelopeV3,
419
420 pub execution_requests: Requests,
424}
425
426#[cfg(feature = "ssz")]
427impl ssz::Encode for ExecutionPayloadEnvelopeV4 {
428 fn is_ssz_fixed_len() -> bool {
429 false
430 }
431
432 fn ssz_append(&self, buf: &mut Vec<u8>) {
433 let offset = <ExecutionPayloadV3 as ssz::Encode>::ssz_fixed_len()
434 + <U256 as ssz::Encode>::ssz_fixed_len()
435 + <BlobsBundleV1 as ssz::Encode>::ssz_fixed_len()
436 + <bool as ssz::Encode>::ssz_fixed_len()
437 + <Requests as ssz::Encode>::ssz_fixed_len();
438 let mut encoder = ssz::SszEncoder::container(buf, offset);
439
440 encoder.append(&self.envelope_inner.execution_payload);
441 encoder.append(&self.envelope_inner.block_value);
442 encoder.append(&self.envelope_inner.blobs_bundle);
443 encoder.append(&self.envelope_inner.should_override_builder);
444 encoder.append(&self.execution_requests);
445
446 encoder.finalize();
447 }
448
449 fn ssz_bytes_len(&self) -> usize {
450 let fixed_section_len = <ExecutionPayloadV3 as ssz::Encode>::ssz_fixed_len()
451 + <U256 as ssz::Encode>::ssz_fixed_len()
452 + <BlobsBundleV1 as ssz::Encode>::ssz_fixed_len()
453 + <bool as ssz::Encode>::ssz_fixed_len()
454 + <Requests as ssz::Encode>::ssz_fixed_len();
455
456 fixed_section_len
457 + self.envelope_inner.execution_payload.ssz_bytes_len()
458 + self.envelope_inner.blobs_bundle.ssz_bytes_len()
459 + self.execution_requests.ssz_bytes_len()
460 }
461}
462
463#[cfg(feature = "ssz")]
464impl ssz::Decode for ExecutionPayloadEnvelopeV4 {
465 fn is_ssz_fixed_len() -> bool {
466 false
467 }
468
469 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
470 let mut builder = ssz::SszDecoderBuilder::new(bytes);
471
472 builder.register_type::<ExecutionPayloadV3>()?;
473 builder.register_type::<U256>()?;
474 builder.register_type::<BlobsBundleV1>()?;
475 builder.register_type::<bool>()?;
476 builder.register_type::<Requests>()?;
477
478 let mut decoder = builder.build()?;
479 Ok(Self {
480 envelope_inner: ExecutionPayloadEnvelopeV3 {
481 execution_payload: decoder.decode_next()?,
482 block_value: decoder.decode_next()?,
483 blobs_bundle: decoder.decode_next()?,
484 should_override_builder: decoder.decode_next()?,
485 },
486 execution_requests: decoder.decode_next()?,
487 })
488 }
489}
490
491impl ExecutionPayloadEnvelopeV4 {
492 pub fn into_payload_and_sidecar(
499 self,
500 parent_beacon_block_root: B256,
501 ) -> (ExecutionPayload, ExecutionPayloadSidecar) {
502 let versioned_hashes = self.blobs_bundle.versioned_hashes();
503
504 let cancun_fields = CancunPayloadFields { parent_beacon_block_root, versioned_hashes };
505 let prague_fields = PraguePayloadFields::new(self.execution_requests);
506
507 (
508 ExecutionPayload::V3(self.envelope_inner.execution_payload),
509 ExecutionPayloadSidecar::v4(cancun_fields, prague_fields),
510 )
511 }
512
513 #[cfg(feature = "kzg")]
523 pub fn try_into_v5(
524 self,
525 ) -> Result<ExecutionPayloadEnvelopeV5, alloy_eips::eip4844::c_kzg::Error> {
526 self.try_into_v5_with_settings(
527 alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
528 )
529 }
530
531 #[cfg(feature = "kzg")]
538 pub fn try_into_v5_with_settings(
539 self,
540 settings: &alloy_eips::eip4844::c_kzg::KzgSettings,
541 ) -> Result<ExecutionPayloadEnvelopeV5, alloy_eips::eip4844::c_kzg::Error> {
542 let blobs_bundle = self.envelope_inner.blobs_bundle.try_into_v2_with_settings(settings)?;
543 Ok(ExecutionPayloadEnvelopeV5 {
544 execution_payload: self.envelope_inner.execution_payload,
545 block_value: self.envelope_inner.block_value,
546 blobs_bundle,
547 should_override_builder: self.envelope_inner.should_override_builder,
548 execution_requests: self.execution_requests,
549 })
550 }
551}
552
553#[cfg(feature = "serde")]
554impl<'de> serde::Deserialize<'de> for ExecutionPayloadEnvelopeV4 {
555 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
556 where
557 D: serde::Deserializer<'de>,
558 {
559 #[derive(serde::Deserialize)]
560 #[serde(rename_all = "camelCase")]
561 struct Helper {
562 execution_payload: ExecutionPayloadV3,
563 block_value: U256,
564 blobs_bundle: BlobsBundleV1,
565 should_override_builder: bool,
566 execution_requests: Requests,
567 }
568
569 let helper = Helper::deserialize(deserializer)?;
570 Ok(Self {
571 envelope_inner: ExecutionPayloadEnvelopeV3 {
572 execution_payload: helper.execution_payload,
573 block_value: helper.block_value,
574 blobs_bundle: helper.blobs_bundle,
575 should_override_builder: helper.should_override_builder,
576 },
577 execution_requests: helper.execution_requests,
578 })
579 }
580}
581
582#[derive(Clone, Debug, PartialEq, Eq)]
588#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
589#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
590#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
591#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
592pub struct ExecutionPayloadEnvelopeV5 {
593 pub execution_payload: ExecutionPayloadV3,
595 pub block_value: U256,
597 pub blobs_bundle: BlobsBundleV2,
600 pub should_override_builder: bool,
603 pub execution_requests: Requests,
607}
608
609#[cfg(feature = "kzg")]
610impl TryFrom<ExecutionPayloadEnvelopeV4> for ExecutionPayloadEnvelopeV5 {
611 type Error = alloy_eips::eip4844::c_kzg::Error;
612
613 fn try_from(value: ExecutionPayloadEnvelopeV4) -> Result<Self, Self::Error> {
614 value.try_into_v5()
615 }
616}
617
618impl ExecutionPayloadEnvelopeV5 {
619 pub fn into_payload_and_sidecar(
626 self,
627 parent_beacon_block_root: B256,
628 ) -> (ExecutionPayload, ExecutionPayloadSidecar) {
629 let versioned_hashes = self.blobs_bundle.versioned_hashes();
630
631 let cancun_fields = CancunPayloadFields { parent_beacon_block_root, versioned_hashes };
632 let prague_fields = PraguePayloadFields::new(self.execution_requests);
633
634 (
635 ExecutionPayload::V3(self.execution_payload),
636 ExecutionPayloadSidecar::v4(cancun_fields, prague_fields),
637 )
638 }
639
640 #[cfg(feature = "kzg")]
650 pub fn try_into_v4(
651 self,
652 ) -> Result<ExecutionPayloadEnvelopeV4, alloy_eips::eip4844::c_kzg::Error> {
653 self.try_into_v4_with_settings(
654 alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
655 )
656 }
657
658 #[cfg(feature = "kzg")]
665 pub fn try_into_v4_with_settings(
666 self,
667 settings: &alloy_eips::eip4844::c_kzg::KzgSettings,
668 ) -> Result<ExecutionPayloadEnvelopeV4, alloy_eips::eip4844::c_kzg::Error> {
669 let blobs_bundle = self.blobs_bundle.try_into_v1_with_settings(settings)?;
670 Ok(ExecutionPayloadEnvelopeV4 {
671 envelope_inner: ExecutionPayloadEnvelopeV3 {
672 execution_payload: self.execution_payload,
673 block_value: self.block_value,
674 blobs_bundle,
675 should_override_builder: self.should_override_builder,
676 },
677 execution_requests: self.execution_requests,
678 })
679 }
680}
681
682#[cfg(feature = "kzg")]
683impl TryFrom<ExecutionPayloadEnvelopeV5> for ExecutionPayloadEnvelopeV4 {
684 type Error = alloy_eips::eip4844::c_kzg::Error;
685
686 fn try_from(value: ExecutionPayloadEnvelopeV5) -> Result<Self, Self::Error> {
687 value.try_into_v4()
688 }
689}
690
691#[derive(Clone, Debug, PartialEq, Eq)]
696#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
697#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
698#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
699#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
700pub struct ExecutionPayloadEnvelopeV6 {
701 pub execution_payload: ExecutionPayloadV4,
703 pub block_value: U256,
705 pub blobs_bundle: BlobsBundleV2,
710 pub should_override_builder: bool,
713 pub execution_requests: Requests,
717}
718
719#[derive(Clone, Debug, PartialEq, Eq)]
723#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
724#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
725#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
726#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
727pub struct ExecutionPayloadV1 {
728 pub parent_hash: B256,
730 pub fee_recipient: Address,
732 pub state_root: B256,
734 pub receipts_root: B256,
736 pub logs_bloom: Bloom,
738 pub prev_randao: B256,
740 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
742 pub block_number: u64,
743 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
745 pub gas_limit: u64,
746 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
748 pub gas_used: u64,
749 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
751 pub timestamp: u64,
752 pub extra_data: Bytes,
754 pub base_fee_per_gas: U256,
756 pub block_hash: B256,
758 pub transactions: Vec<Bytes>,
760}
761
762impl ExecutionPayloadV1 {
763 pub const fn block_num_hash(&self) -> BlockNumHash {
765 BlockNumHash::new(self.block_number, self.block_hash)
766 }
767
768 pub fn try_into_block<T: Decodable2718>(self) -> Result<Block<T>, PayloadError> {
773 self.try_into_block_with(|tx| {
774 T::decode_2718_exact(tx.as_ref())
775 .map_err(alloy_rlp::Error::from)
776 .map_err(PayloadError::from)
777 })
778 }
779
780 pub fn try_into_block_with<T, F, E>(self, f: F) -> Result<Block<T>, PayloadError>
782 where
783 F: FnMut(Bytes) -> Result<T, E>,
784 E: Into<PayloadError>,
785 {
786 self.into_block_raw()?.try_map_transactions(f).map_err(Into::into)
787 }
788
789 pub fn into_block_raw(self) -> Result<Block<Bytes>, PayloadError> {
794 self.into_block_raw_with_transactions_root_opt(None)
795 }
796
797 pub fn into_block_raw_with_transactions_root(
803 self,
804 transactions_root: B256,
805 ) -> Result<Block<Bytes>, PayloadError> {
806 self.into_block_raw_with_transactions_root_opt(Some(transactions_root))
807 }
808
809 pub fn into_block_raw_with_transactions_root_opt(
814 self,
815 transactions_root: Option<B256>,
816 ) -> Result<Block<Bytes>, PayloadError> {
817 if self.extra_data.len() > MAXIMUM_EXTRA_DATA_SIZE {
818 return Err(PayloadError::ExtraData(self.extra_data));
819 }
820
821 let transactions_root = transactions_root.unwrap_or_else(|| {
822 alloy_consensus::proofs::ordered_trie_root_encoded(&self.transactions)
823 });
824
825 let header = Header {
826 parent_hash: self.parent_hash,
827 beneficiary: self.fee_recipient,
828 state_root: self.state_root,
829 transactions_root,
830 receipts_root: self.receipts_root,
831 withdrawals_root: None,
832 logs_bloom: self.logs_bloom,
833 number: self.block_number,
834 gas_limit: self.gas_limit,
835 gas_used: self.gas_used,
836 timestamp: self.timestamp,
837 mix_hash: self.prev_randao,
838 base_fee_per_gas: Some(
843 self.base_fee_per_gas
844 .try_into()
845 .map_err(|_| PayloadError::BaseFee(self.base_fee_per_gas))?,
846 ),
847 blob_gas_used: None,
848 excess_blob_gas: None,
849 parent_beacon_block_root: None,
850 requests_hash: None,
851 block_access_list_hash: None,
852 slot_number: None,
853 extra_data: self.extra_data,
854 ommers_hash: EMPTY_OMMER_ROOT_HASH,
856 difficulty: Default::default(),
857 nonce: Default::default(),
858 };
859
860 Ok(Block {
861 header,
862 body: BlockBody { transactions: self.transactions, ommers: vec![], withdrawals: None },
863 })
864 }
865
866 pub fn from_block_slow<T, H>(block: &Block<T, H>) -> Self
870 where
871 T: Encodable2718,
872 H: BlockHeader + Sealable,
873 {
874 Self::from_block_unchecked(block.header.hash_slow(), block)
875 }
876
877 pub fn from_block_unchecked<T, H>(block_hash: B256, block: &Block<T, H>) -> Self
881 where
882 T: Encodable2718,
883 H: BlockHeader,
884 {
885 let transactions =
886 block.body.transactions().map(|tx| tx.encoded_2718().into()).collect::<Vec<_>>();
887 Self {
888 parent_hash: block.parent_hash(),
889 fee_recipient: block.beneficiary(),
890 state_root: block.state_root(),
891 receipts_root: block.receipts_root(),
892 logs_bloom: block.logs_bloom(),
893 prev_randao: block.mix_hash().unwrap_or_default(),
894 block_number: block.number(),
895 gas_limit: block.gas_limit(),
896 gas_used: block.gas_used(),
897 timestamp: block.timestamp(),
898 base_fee_per_gas: U256::from(block.base_fee_per_gas().unwrap_or_default()),
899 extra_data: block.header.extra_data().clone(),
900 block_hash,
901 transactions,
902 }
903 }
904
905 pub fn next_block_base_fee(&self, base_fee_params: BaseFeeParams) -> Option<u64> {
909 Some(calc_next_block_base_fee(
910 self.gas_used,
911 self.gas_limit,
912 self.base_fee_per_gas.try_into().ok()?,
913 base_fee_params,
914 ))
915 }
916}
917
918impl<T: Decodable2718> TryFrom<ExecutionPayloadV1> for Block<T> {
919 type Error = PayloadError;
920
921 fn try_from(value: ExecutionPayloadV1) -> Result<Self, Self::Error> {
922 value.try_into_block()
923 }
924}
925
926#[derive(Clone, Debug, PartialEq, Eq)]
930#[cfg_attr(feature = "serde", derive(serde::Serialize))]
931#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
932#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
933pub struct ExecutionPayloadV2 {
934 #[cfg_attr(feature = "serde", serde(flatten))]
936 pub payload_inner: ExecutionPayloadV1,
937
938 pub withdrawals: Vec<Withdrawal>,
941}
942
943#[cfg(feature = "serde")]
944impl<'de> serde::Deserialize<'de> for ExecutionPayloadV2 {
945 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
946 where
947 D: serde::Deserializer<'de>,
948 {
949 #[derive(serde::Deserialize)]
950 #[serde(rename_all = "camelCase")]
951 struct Helper {
952 parent_hash: B256,
953 fee_recipient: Address,
954 state_root: B256,
955 receipts_root: B256,
956 logs_bloom: Bloom,
957 prev_randao: B256,
958 #[serde(with = "alloy_serde::quantity")]
959 block_number: u64,
960 #[serde(with = "alloy_serde::quantity")]
961 gas_limit: u64,
962 #[serde(with = "alloy_serde::quantity")]
963 gas_used: u64,
964 #[serde(with = "alloy_serde::quantity")]
965 timestamp: u64,
966 extra_data: Bytes,
967 base_fee_per_gas: U256,
968 block_hash: B256,
969 transactions: Vec<Bytes>,
970 withdrawals: Vec<Withdrawal>,
971 }
972
973 let helper = Helper::deserialize(deserializer)?;
974 Ok(Self {
975 payload_inner: ExecutionPayloadV1 {
976 parent_hash: helper.parent_hash,
977 fee_recipient: helper.fee_recipient,
978 state_root: helper.state_root,
979 receipts_root: helper.receipts_root,
980 logs_bloom: helper.logs_bloom,
981 prev_randao: helper.prev_randao,
982 block_number: helper.block_number,
983 gas_limit: helper.gas_limit,
984 gas_used: helper.gas_used,
985 timestamp: helper.timestamp,
986 extra_data: helper.extra_data,
987 base_fee_per_gas: helper.base_fee_per_gas,
988 block_hash: helper.block_hash,
989 transactions: helper.transactions,
990 },
991 withdrawals: helper.withdrawals,
992 })
993 }
994}
995
996impl ExecutionPayloadV2 {
997 pub fn from_block_slow<T, H>(block: &Block<T, H>) -> Self
1005 where
1006 T: Encodable2718,
1007 H: BlockHeader + Sealable,
1008 {
1009 Self::from_block_unchecked(block.header.hash_slow(), block)
1010 }
1011
1012 pub fn from_block_unchecked<T, H>(block_hash: B256, block: &Block<T, H>) -> Self
1018 where
1019 T: Encodable2718,
1020 H: BlockHeader,
1021 {
1022 Self {
1023 withdrawals: block
1024 .body
1025 .withdrawals
1026 .clone()
1027 .map(Withdrawals::into_inner)
1028 .unwrap_or_default(),
1029 payload_inner: ExecutionPayloadV1::from_block_unchecked(block_hash, block),
1030 }
1031 }
1032
1033 pub const fn timestamp(&self) -> u64 {
1035 self.payload_inner.timestamp
1036 }
1037
1038 pub fn into_payload_input_v2(self, is_shanghai_active: bool) -> ExecutionPayloadInputV2 {
1046 ExecutionPayloadInputV2 {
1047 execution_payload: self.payload_inner,
1048 withdrawals: is_shanghai_active.then_some(self.withdrawals),
1049 }
1050 }
1051
1052 pub fn try_into_block<T: Decodable2718>(self) -> Result<Block<T>, PayloadError> {
1059 self.try_into_block_with(|tx| {
1060 T::decode_2718_exact(tx.as_ref())
1061 .map_err(alloy_rlp::Error::from)
1062 .map_err(PayloadError::from)
1063 })
1064 }
1065
1066 pub fn try_into_block_with<T, F, E>(self, f: F) -> Result<Block<T>, PayloadError>
1070 where
1071 F: FnMut(Bytes) -> Result<T, E>,
1072 E: Into<PayloadError>,
1073 {
1074 self.into_block_raw()?.try_map_transactions(f).map_err(Into::into)
1075 }
1076
1077 pub fn into_block_raw(self) -> Result<Block<Bytes>, PayloadError> {
1082 self.into_block_raw_with_transactions_root_opt(None)
1083 }
1084
1085 pub fn into_block_raw_with_transactions_root(
1090 self,
1091 transactions_root: B256,
1092 ) -> Result<Block<Bytes>, PayloadError> {
1093 self.into_block_raw_with_transactions_root_opt(Some(transactions_root))
1094 }
1095
1096 pub fn into_block_raw_with_transactions_root_opt(
1101 self,
1102 transactions_root: Option<B256>,
1103 ) -> Result<Block<Bytes>, PayloadError> {
1104 let mut base_sealed_block =
1105 self.payload_inner.into_block_raw_with_transactions_root_opt(transactions_root)?;
1106 let withdrawals_root =
1107 alloy_consensus::proofs::calculate_withdrawals_root(&self.withdrawals);
1108 base_sealed_block.body.withdrawals = Some(self.withdrawals.into());
1109 base_sealed_block.header.withdrawals_root = Some(withdrawals_root);
1110 Ok(base_sealed_block)
1111 }
1112}
1113
1114impl<T: Decodable2718> TryFrom<ExecutionPayloadV2> for Block<T> {
1115 type Error = PayloadError;
1116
1117 fn try_from(value: ExecutionPayloadV2) -> Result<Self, Self::Error> {
1118 value.try_into_block()
1119 }
1120}
1121
1122#[cfg(feature = "ssz")]
1123impl ssz::Decode for ExecutionPayloadV2 {
1124 fn is_ssz_fixed_len() -> bool {
1125 false
1126 }
1127
1128 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
1129 let mut builder = ssz::SszDecoderBuilder::new(bytes);
1130
1131 builder.register_type::<B256>()?;
1132 builder.register_type::<Address>()?;
1133 builder.register_type::<B256>()?;
1134 builder.register_type::<B256>()?;
1135 builder.register_type::<Bloom>()?;
1136 builder.register_type::<B256>()?;
1137 builder.register_type::<u64>()?;
1138 builder.register_type::<u64>()?;
1139 builder.register_type::<u64>()?;
1140 builder.register_type::<u64>()?;
1141 builder.register_type::<Bytes>()?;
1142 builder.register_type::<U256>()?;
1143 builder.register_type::<B256>()?;
1144 builder.register_type::<Vec<Bytes>>()?;
1145 builder.register_type::<Vec<Withdrawal>>()?;
1146
1147 let mut decoder = builder.build()?;
1148
1149 Ok(Self {
1150 payload_inner: ExecutionPayloadV1 {
1151 parent_hash: decoder.decode_next()?,
1152 fee_recipient: decoder.decode_next()?,
1153 state_root: decoder.decode_next()?,
1154 receipts_root: decoder.decode_next()?,
1155 logs_bloom: decoder.decode_next()?,
1156 prev_randao: decoder.decode_next()?,
1157 block_number: decoder.decode_next()?,
1158 gas_limit: decoder.decode_next()?,
1159 gas_used: decoder.decode_next()?,
1160 timestamp: decoder.decode_next()?,
1161 extra_data: decoder.decode_next()?,
1162 base_fee_per_gas: decoder.decode_next()?,
1163 block_hash: decoder.decode_next()?,
1164 transactions: decoder.decode_next()?,
1165 },
1166 withdrawals: decoder.decode_next()?,
1167 })
1168 }
1169}
1170
1171#[cfg(feature = "ssz")]
1172impl ssz::Encode for ExecutionPayloadV2 {
1173 fn is_ssz_fixed_len() -> bool {
1174 false
1175 }
1176
1177 fn ssz_append(&self, buf: &mut Vec<u8>) {
1178 let offset = <B256 as ssz::Encode>::ssz_fixed_len() * 5
1179 + <Address as ssz::Encode>::ssz_fixed_len()
1180 + <Bloom as ssz::Encode>::ssz_fixed_len()
1181 + <u64 as ssz::Encode>::ssz_fixed_len() * 4
1182 + <U256 as ssz::Encode>::ssz_fixed_len()
1183 + ssz::BYTES_PER_LENGTH_OFFSET * 3;
1184
1185 let mut encoder = ssz::SszEncoder::container(buf, offset);
1186
1187 encoder.append(&self.payload_inner.parent_hash);
1188 encoder.append(&self.payload_inner.fee_recipient);
1189 encoder.append(&self.payload_inner.state_root);
1190 encoder.append(&self.payload_inner.receipts_root);
1191 encoder.append(&self.payload_inner.logs_bloom);
1192 encoder.append(&self.payload_inner.prev_randao);
1193 encoder.append(&self.payload_inner.block_number);
1194 encoder.append(&self.payload_inner.gas_limit);
1195 encoder.append(&self.payload_inner.gas_used);
1196 encoder.append(&self.payload_inner.timestamp);
1197 encoder.append(&self.payload_inner.extra_data);
1198 encoder.append(&self.payload_inner.base_fee_per_gas);
1199 encoder.append(&self.payload_inner.block_hash);
1200 encoder.append(&self.payload_inner.transactions);
1201 encoder.append(&self.withdrawals);
1202
1203 encoder.finalize();
1204 }
1205
1206 fn ssz_bytes_len(&self) -> usize {
1207 <ExecutionPayloadV1 as ssz::Encode>::ssz_bytes_len(&self.payload_inner)
1208 + ssz::BYTES_PER_LENGTH_OFFSET
1209 + self.withdrawals.ssz_bytes_len()
1210 }
1211}
1212
1213#[derive(Clone, Debug, PartialEq, Eq)]
1217#[cfg_attr(feature = "serde", derive(serde::Serialize))]
1218#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1219#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
1220pub struct ExecutionPayloadV3 {
1221 #[cfg_attr(feature = "serde", serde(flatten))]
1223 pub payload_inner: ExecutionPayloadV2,
1224
1225 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
1228 pub blob_gas_used: u64,
1229 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
1232 pub excess_blob_gas: u64,
1233}
1234
1235#[cfg(feature = "serde")]
1236impl<'de> serde::Deserialize<'de> for ExecutionPayloadV3 {
1237 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1238 where
1239 D: serde::Deserializer<'de>,
1240 {
1241 #[derive(serde::Deserialize)]
1242 #[serde(rename_all = "camelCase")]
1243 struct Helper {
1244 parent_hash: B256,
1245 fee_recipient: Address,
1246 state_root: B256,
1247 receipts_root: B256,
1248 logs_bloom: Bloom,
1249 prev_randao: B256,
1250 #[serde(with = "alloy_serde::quantity")]
1251 block_number: u64,
1252 #[serde(with = "alloy_serde::quantity")]
1253 gas_limit: u64,
1254 #[serde(with = "alloy_serde::quantity")]
1255 gas_used: u64,
1256 #[serde(with = "alloy_serde::quantity")]
1257 timestamp: u64,
1258 extra_data: Bytes,
1259 base_fee_per_gas: U256,
1260 block_hash: B256,
1261 transactions: Vec<Bytes>,
1262 withdrawals: Vec<Withdrawal>,
1263 #[serde(with = "alloy_serde::quantity")]
1264 blob_gas_used: u64,
1265 #[serde(with = "alloy_serde::quantity")]
1266 excess_blob_gas: u64,
1267 }
1268
1269 let helper = Helper::deserialize(deserializer)?;
1270 Ok(Self {
1271 payload_inner: ExecutionPayloadV2 {
1272 payload_inner: ExecutionPayloadV1 {
1273 parent_hash: helper.parent_hash,
1274 fee_recipient: helper.fee_recipient,
1275 state_root: helper.state_root,
1276 receipts_root: helper.receipts_root,
1277 logs_bloom: helper.logs_bloom,
1278 prev_randao: helper.prev_randao,
1279 block_number: helper.block_number,
1280 gas_limit: helper.gas_limit,
1281 gas_used: helper.gas_used,
1282 timestamp: helper.timestamp,
1283 extra_data: helper.extra_data,
1284 base_fee_per_gas: helper.base_fee_per_gas,
1285 block_hash: helper.block_hash,
1286 transactions: helper.transactions,
1287 },
1288 withdrawals: helper.withdrawals,
1289 },
1290 blob_gas_used: helper.blob_gas_used,
1291 excess_blob_gas: helper.excess_blob_gas,
1292 })
1293 }
1294}
1295
1296impl ExecutionPayloadV3 {
1297 pub fn from_block_slow<T, H>(block: &Block<T, H>) -> Self
1303 where
1304 T: Encodable2718,
1305 H: BlockHeader + Sealable,
1306 {
1307 Self::from_block_unchecked(block.hash_slow(), block)
1308 }
1309
1310 pub fn from_block_unchecked<T, H>(block_hash: B256, block: &Block<T, H>) -> Self
1314 where
1315 T: Encodable2718,
1316 H: BlockHeader,
1317 {
1318 Self {
1319 blob_gas_used: block.blob_gas_used().unwrap_or_default(),
1320 excess_blob_gas: block.excess_blob_gas().unwrap_or_default(),
1321 payload_inner: ExecutionPayloadV2::from_block_unchecked(block_hash, block),
1322 }
1323 }
1324
1325 pub const fn withdrawals(&self) -> &Vec<Withdrawal> {
1327 &self.payload_inner.withdrawals
1328 }
1329
1330 pub const fn timestamp(&self) -> u64 {
1332 self.payload_inner.payload_inner.timestamp
1333 }
1334
1335 pub fn try_into_block<T: Decodable2718>(self) -> Result<Block<T>, PayloadError> {
1342 self.try_into_block_with(|tx| {
1343 T::decode_2718_exact(tx.as_ref())
1344 .map_err(alloy_rlp::Error::from)
1345 .map_err(PayloadError::from)
1346 })
1347 }
1348
1349 pub fn try_into_block_with<T, F, E>(self, f: F) -> Result<Block<T>, PayloadError>
1353 where
1354 F: FnMut(Bytes) -> Result<T, E>,
1355 E: Into<PayloadError>,
1356 {
1357 self.into_block_raw()?.try_map_transactions(f).map_err(Into::into)
1358 }
1359
1360 pub fn into_block_raw(self) -> Result<Block<Bytes>, PayloadError> {
1365 self.into_block_raw_with_transactions_root_opt(None)
1366 }
1367
1368 pub fn into_block_raw_with_transactions_root(
1373 self,
1374 transactions_root: B256,
1375 ) -> Result<Block<Bytes>, PayloadError> {
1376 self.into_block_raw_with_transactions_root_opt(Some(transactions_root))
1377 }
1378
1379 pub fn into_block_raw_with_transactions_root_opt(
1384 self,
1385 transactions_root: Option<B256>,
1386 ) -> Result<Block<Bytes>, PayloadError> {
1387 let mut base_block =
1388 self.payload_inner.into_block_raw_with_transactions_root_opt(transactions_root)?;
1389
1390 base_block.header.blob_gas_used = Some(self.blob_gas_used);
1391 base_block.header.excess_blob_gas = Some(self.excess_blob_gas);
1392
1393 Ok(base_block)
1394 }
1395}
1396
1397impl<T: Decodable2718> TryFrom<ExecutionPayloadV3> for Block<T> {
1398 type Error = PayloadError;
1399
1400 fn try_from(value: ExecutionPayloadV3) -> Result<Self, Self::Error> {
1401 value.try_into_block()
1402 }
1403}
1404
1405#[cfg(feature = "ssz")]
1406impl ssz::Decode for ExecutionPayloadV3 {
1407 fn is_ssz_fixed_len() -> bool {
1408 false
1409 }
1410
1411 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
1412 let mut builder = ssz::SszDecoderBuilder::new(bytes);
1413
1414 builder.register_type::<B256>()?;
1415 builder.register_type::<Address>()?;
1416 builder.register_type::<B256>()?;
1417 builder.register_type::<B256>()?;
1418 builder.register_type::<Bloom>()?;
1419 builder.register_type::<B256>()?;
1420 builder.register_type::<u64>()?;
1421 builder.register_type::<u64>()?;
1422 builder.register_type::<u64>()?;
1423 builder.register_type::<u64>()?;
1424 builder.register_type::<Bytes>()?;
1425 builder.register_type::<U256>()?;
1426 builder.register_type::<B256>()?;
1427 builder.register_type::<Vec<Bytes>>()?;
1428 builder.register_type::<Vec<Withdrawal>>()?;
1429 builder.register_type::<u64>()?;
1430 builder.register_type::<u64>()?;
1431
1432 let mut decoder = builder.build()?;
1433
1434 Ok(Self {
1435 payload_inner: ExecutionPayloadV2 {
1436 payload_inner: ExecutionPayloadV1 {
1437 parent_hash: decoder.decode_next()?,
1438 fee_recipient: decoder.decode_next()?,
1439 state_root: decoder.decode_next()?,
1440 receipts_root: decoder.decode_next()?,
1441 logs_bloom: decoder.decode_next()?,
1442 prev_randao: decoder.decode_next()?,
1443 block_number: decoder.decode_next()?,
1444 gas_limit: decoder.decode_next()?,
1445 gas_used: decoder.decode_next()?,
1446 timestamp: decoder.decode_next()?,
1447 extra_data: decoder.decode_next()?,
1448 base_fee_per_gas: decoder.decode_next()?,
1449 block_hash: decoder.decode_next()?,
1450 transactions: decoder.decode_next()?,
1451 },
1452 withdrawals: decoder.decode_next()?,
1453 },
1454 blob_gas_used: decoder.decode_next()?,
1455 excess_blob_gas: decoder.decode_next()?,
1456 })
1457 }
1458}
1459
1460#[cfg(feature = "ssz")]
1461impl ssz::Encode for ExecutionPayloadV3 {
1462 fn is_ssz_fixed_len() -> bool {
1463 false
1464 }
1465
1466 fn ssz_append(&self, buf: &mut Vec<u8>) {
1467 let offset = <B256 as ssz::Encode>::ssz_fixed_len() * 5
1468 + <Address as ssz::Encode>::ssz_fixed_len()
1469 + <Bloom as ssz::Encode>::ssz_fixed_len()
1470 + <u64 as ssz::Encode>::ssz_fixed_len() * 6
1471 + <U256 as ssz::Encode>::ssz_fixed_len()
1472 + ssz::BYTES_PER_LENGTH_OFFSET * 3;
1473
1474 let mut encoder = ssz::SszEncoder::container(buf, offset);
1475
1476 encoder.append(&self.payload_inner.payload_inner.parent_hash);
1477 encoder.append(&self.payload_inner.payload_inner.fee_recipient);
1478 encoder.append(&self.payload_inner.payload_inner.state_root);
1479 encoder.append(&self.payload_inner.payload_inner.receipts_root);
1480 encoder.append(&self.payload_inner.payload_inner.logs_bloom);
1481 encoder.append(&self.payload_inner.payload_inner.prev_randao);
1482 encoder.append(&self.payload_inner.payload_inner.block_number);
1483 encoder.append(&self.payload_inner.payload_inner.gas_limit);
1484 encoder.append(&self.payload_inner.payload_inner.gas_used);
1485 encoder.append(&self.payload_inner.payload_inner.timestamp);
1486 encoder.append(&self.payload_inner.payload_inner.extra_data);
1487 encoder.append(&self.payload_inner.payload_inner.base_fee_per_gas);
1488 encoder.append(&self.payload_inner.payload_inner.block_hash);
1489 encoder.append(&self.payload_inner.payload_inner.transactions);
1490 encoder.append(&self.payload_inner.withdrawals);
1491 encoder.append(&self.blob_gas_used);
1492 encoder.append(&self.excess_blob_gas);
1493
1494 encoder.finalize();
1495 }
1496
1497 fn ssz_bytes_len(&self) -> usize {
1498 <ExecutionPayloadV2 as ssz::Encode>::ssz_bytes_len(&self.payload_inner)
1499 + <u64 as ssz::Encode>::ssz_fixed_len() * 2
1500 }
1501}
1502
1503#[derive(Clone, Debug, PartialEq, Eq)]
1514#[cfg_attr(feature = "serde", derive(serde::Serialize))]
1515#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1516#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
1517pub struct ExecutionPayloadV4 {
1518 #[cfg_attr(feature = "serde", serde(flatten))]
1520 pub payload_inner: ExecutionPayloadV3,
1521 pub block_access_list: Bytes,
1525 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
1529 pub slot_number: u64,
1530}
1531
1532#[cfg(feature = "serde")]
1533impl<'de> serde::Deserialize<'de> for ExecutionPayloadV4 {
1534 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1535 where
1536 D: serde::Deserializer<'de>,
1537 {
1538 #[derive(serde::Deserialize)]
1539 #[serde(rename_all = "camelCase")]
1540 struct Helper {
1541 parent_hash: B256,
1542 fee_recipient: Address,
1543 state_root: B256,
1544 receipts_root: B256,
1545 logs_bloom: Bloom,
1546 prev_randao: B256,
1547 #[serde(with = "alloy_serde::quantity")]
1548 block_number: u64,
1549 #[serde(with = "alloy_serde::quantity")]
1550 gas_limit: u64,
1551 #[serde(with = "alloy_serde::quantity")]
1552 gas_used: u64,
1553 #[serde(with = "alloy_serde::quantity")]
1554 timestamp: u64,
1555 extra_data: Bytes,
1556 base_fee_per_gas: U256,
1557 block_hash: B256,
1558 transactions: Vec<Bytes>,
1559 withdrawals: Vec<Withdrawal>,
1560 #[serde(with = "alloy_serde::quantity")]
1561 blob_gas_used: u64,
1562 #[serde(with = "alloy_serde::quantity")]
1563 excess_blob_gas: u64,
1564 block_access_list: Bytes,
1565 #[serde(with = "alloy_serde::quantity")]
1566 slot_number: u64,
1567 }
1568
1569 let helper = Helper::deserialize(deserializer)?;
1570 Ok(Self {
1571 payload_inner: ExecutionPayloadV3 {
1572 payload_inner: ExecutionPayloadV2 {
1573 payload_inner: ExecutionPayloadV1 {
1574 parent_hash: helper.parent_hash,
1575 fee_recipient: helper.fee_recipient,
1576 state_root: helper.state_root,
1577 receipts_root: helper.receipts_root,
1578 logs_bloom: helper.logs_bloom,
1579 prev_randao: helper.prev_randao,
1580 block_number: helper.block_number,
1581 gas_limit: helper.gas_limit,
1582 gas_used: helper.gas_used,
1583 timestamp: helper.timestamp,
1584 extra_data: helper.extra_data,
1585 base_fee_per_gas: helper.base_fee_per_gas,
1586 block_hash: helper.block_hash,
1587 transactions: helper.transactions,
1588 },
1589 withdrawals: helper.withdrawals,
1590 },
1591 blob_gas_used: helper.blob_gas_used,
1592 excess_blob_gas: helper.excess_blob_gas,
1593 },
1594 block_access_list: helper.block_access_list,
1595 slot_number: helper.slot_number,
1596 })
1597 }
1598}
1599
1600#[cfg(feature = "ssz")]
1601impl ssz::Decode for ExecutionPayloadV4 {
1602 fn is_ssz_fixed_len() -> bool {
1603 false
1604 }
1605
1606 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
1607 let mut builder = ssz::SszDecoderBuilder::new(bytes);
1608
1609 builder.register_type::<B256>()?;
1610 builder.register_type::<Address>()?;
1611 builder.register_type::<B256>()?;
1612 builder.register_type::<B256>()?;
1613 builder.register_type::<Bloom>()?;
1614 builder.register_type::<B256>()?;
1615 builder.register_type::<u64>()?;
1616 builder.register_type::<u64>()?;
1617 builder.register_type::<u64>()?;
1618 builder.register_type::<u64>()?;
1619 builder.register_type::<Bytes>()?;
1620 builder.register_type::<U256>()?;
1621 builder.register_type::<B256>()?;
1622 builder.register_type::<Vec<Bytes>>()?;
1623 builder.register_type::<Vec<Withdrawal>>()?;
1624 builder.register_type::<u64>()?;
1625 builder.register_type::<u64>()?;
1626 builder.register_type::<Bytes>()?;
1627 builder.register_type::<u64>()?;
1628
1629 let mut decoder = builder.build()?;
1630
1631 Ok(Self {
1632 payload_inner: ExecutionPayloadV3 {
1633 payload_inner: ExecutionPayloadV2 {
1634 payload_inner: ExecutionPayloadV1 {
1635 parent_hash: decoder.decode_next()?,
1636 fee_recipient: decoder.decode_next()?,
1637 state_root: decoder.decode_next()?,
1638 receipts_root: decoder.decode_next()?,
1639 logs_bloom: decoder.decode_next()?,
1640 prev_randao: decoder.decode_next()?,
1641 block_number: decoder.decode_next()?,
1642 gas_limit: decoder.decode_next()?,
1643 gas_used: decoder.decode_next()?,
1644 timestamp: decoder.decode_next()?,
1645 extra_data: decoder.decode_next()?,
1646 base_fee_per_gas: decoder.decode_next()?,
1647 block_hash: decoder.decode_next()?,
1648 transactions: decoder.decode_next()?,
1649 },
1650 withdrawals: decoder.decode_next()?,
1651 },
1652 blob_gas_used: decoder.decode_next()?,
1653 excess_blob_gas: decoder.decode_next()?,
1654 },
1655 block_access_list: decoder.decode_next()?,
1656 slot_number: decoder.decode_next()?,
1657 })
1658 }
1659}
1660
1661#[cfg(feature = "ssz")]
1662impl ssz::Encode for ExecutionPayloadV4 {
1663 fn is_ssz_fixed_len() -> bool {
1664 false
1665 }
1666
1667 fn ssz_append(&self, buf: &mut Vec<u8>) {
1668 let offset = <B256 as ssz::Encode>::ssz_fixed_len() * 5
1669 + <Address as ssz::Encode>::ssz_fixed_len()
1670 + <Bloom as ssz::Encode>::ssz_fixed_len()
1671 + <u64 as ssz::Encode>::ssz_fixed_len() * 7 + <U256 as ssz::Encode>::ssz_fixed_len()
1673 + ssz::BYTES_PER_LENGTH_OFFSET * 4;
1674
1675 let mut encoder = ssz::SszEncoder::container(buf, offset);
1676
1677 encoder.append(&self.payload_inner.payload_inner.payload_inner.parent_hash);
1678 encoder.append(&self.payload_inner.payload_inner.payload_inner.fee_recipient);
1679 encoder.append(&self.payload_inner.payload_inner.payload_inner.state_root);
1680 encoder.append(&self.payload_inner.payload_inner.payload_inner.receipts_root);
1681 encoder.append(&self.payload_inner.payload_inner.payload_inner.logs_bloom);
1682 encoder.append(&self.payload_inner.payload_inner.payload_inner.prev_randao);
1683 encoder.append(&self.payload_inner.payload_inner.payload_inner.block_number);
1684 encoder.append(&self.payload_inner.payload_inner.payload_inner.gas_limit);
1685 encoder.append(&self.payload_inner.payload_inner.payload_inner.gas_used);
1686 encoder.append(&self.payload_inner.payload_inner.payload_inner.timestamp);
1687 encoder.append(&self.payload_inner.payload_inner.payload_inner.extra_data);
1688 encoder.append(&self.payload_inner.payload_inner.payload_inner.base_fee_per_gas);
1689 encoder.append(&self.payload_inner.payload_inner.payload_inner.block_hash);
1690 encoder.append(&self.payload_inner.payload_inner.payload_inner.transactions);
1691 encoder.append(&self.payload_inner.payload_inner.withdrawals);
1692 encoder.append(&self.payload_inner.blob_gas_used);
1693 encoder.append(&self.payload_inner.excess_blob_gas);
1694 encoder.append(&self.block_access_list);
1695 encoder.append(&self.slot_number);
1696
1697 encoder.finalize();
1698 }
1699
1700 fn ssz_bytes_len(&self) -> usize {
1701 <ExecutionPayloadV3 as ssz::Encode>::ssz_bytes_len(&self.payload_inner)
1702 + ssz::BYTES_PER_LENGTH_OFFSET
1703 + self.block_access_list.len()
1704 + <u64 as ssz::Encode>::ssz_fixed_len()
1705 }
1706}
1707
1708impl ExecutionPayloadV4 {
1709 pub fn from_block_slow<T, H>(block: &Block<T, H>) -> Self
1722 where
1723 T: Encodable2718,
1724 H: BlockHeader + Sealable,
1725 {
1726 Self::from_block_unchecked(block.header.hash_slow(), block)
1727 }
1728
1729 pub fn from_block_unchecked<T, H>(block_hash: B256, block: &Block<T, H>) -> Self
1740 where
1741 T: Encodable2718,
1742 H: BlockHeader,
1743 {
1744 Self {
1745 payload_inner: ExecutionPayloadV3::from_block_unchecked(block_hash, block),
1746 block_access_list: block.header.block_access_list_hash().map_or_else(
1747 || Bytes::copy_from_slice(EMPTY_BLOCK_ACCESS_LIST_HASH.as_slice()),
1748 |hash| Bytes::copy_from_slice(hash.as_slice()),
1749 ),
1750 slot_number: block.header.slot_number().unwrap_or_default(),
1751 }
1752 }
1753
1754 pub fn from_block_unchecked_with_bal<T, H>(
1762 block_hash: B256,
1763 block: &Block<T, H>,
1764 block_access_list: Bytes,
1765 ) -> Self
1766 where
1767 T: Encodable2718,
1768 H: BlockHeader,
1769 {
1770 Self {
1771 payload_inner: ExecutionPayloadV3::from_block_unchecked(block_hash, block),
1772 block_access_list,
1773 slot_number: block.header.slot_number().unwrap_or_default(),
1774 }
1775 }
1776
1777 pub const fn timestamp(&self) -> u64 {
1779 self.payload_inner.timestamp()
1780 }
1781
1782 pub fn try_into_block<T: Decodable2718>(self) -> Result<Block<T>, PayloadError> {
1789 self.try_into_block_with(|tx| {
1790 T::decode_2718_exact(tx.as_ref())
1791 .map_err(alloy_rlp::Error::from)
1792 .map_err(PayloadError::from)
1793 })
1794 }
1795
1796 pub fn try_into_block_with<T, F, E>(self, f: F) -> Result<Block<T>, PayloadError>
1800 where
1801 F: FnMut(Bytes) -> Result<T, E>,
1802 E: Into<PayloadError>,
1803 {
1804 self.into_block_raw()?.try_map_transactions(f).map_err(Into::into)
1805 }
1806
1807 pub fn into_block_raw(self) -> Result<Block<Bytes>, PayloadError> {
1812 let mut base_block = self.payload_inner.into_block_raw()?;
1813
1814 let block_access_list_hash = alloy_primitives::keccak256(&self.block_access_list);
1815 base_block.header.block_access_list_hash = Some(block_access_list_hash);
1816 base_block.header.slot_number = Some(self.slot_number);
1817
1818 Ok(base_block)
1819 }
1820
1821 pub fn into_block_raw_with_transactions_root(
1826 self,
1827 transactions_root: B256,
1828 ) -> Result<Block<Bytes>, PayloadError> {
1829 self.into_block_raw_with_transactions_root_opt(Some(transactions_root))
1830 }
1831
1832 pub fn into_block_raw_with_transactions_root_opt(
1837 self,
1838 transactions_root: Option<B256>,
1839 ) -> Result<Block<Bytes>, PayloadError> {
1840 let mut base_block =
1841 self.payload_inner.into_block_raw_with_transactions_root_opt(transactions_root)?;
1842
1843 base_block.header.block_access_list_hash = Some(keccak256(self.block_access_list));
1844 base_block.header.slot_number = Some(self.slot_number);
1845
1846 Ok(base_block)
1847 }
1848}
1849
1850impl<T: Decodable2718> TryFrom<ExecutionPayloadV4> for Block<T> {
1851 type Error = PayloadError;
1852
1853 fn try_from(value: ExecutionPayloadV4) -> Result<Self, Self::Error> {
1854 value.try_into_block()
1855 }
1856}
1857
1858#[derive(Clone, Debug, Default, PartialEq, Eq)]
1860#[cfg_attr(feature = "serde", derive(serde::Serialize))]
1861#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
1862#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
1863pub struct BlobsBundleV1 {
1864 pub commitments: Vec<alloy_consensus::Bytes48>,
1866 pub proofs: Vec<alloy_consensus::Bytes48>,
1868 pub blobs: Vec<alloy_consensus::Blob>,
1870}
1871
1872#[cfg(feature = "serde")]
1873impl<'de> serde::Deserialize<'de> for BlobsBundleV1 {
1874 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1875 where
1876 D: serde::Deserializer<'de>,
1877 {
1878 #[derive(serde::Deserialize)]
1879 struct BlobsBundleRaw {
1880 commitments: Vec<alloy_consensus::Bytes48>,
1881 proofs: Vec<alloy_consensus::Bytes48>,
1882 blobs: Vec<alloy_consensus::Blob>,
1883 }
1884 let raw = BlobsBundleRaw::deserialize(deserializer)?;
1885
1886 if raw.proofs.len() == raw.commitments.len() && raw.proofs.len() == raw.blobs.len() {
1887 Ok(Self { commitments: raw.commitments, proofs: raw.proofs, blobs: raw.blobs })
1888 } else {
1889 Err(serde::de::Error::invalid_length(
1890 raw.proofs.len(),
1891 &format!("{}", raw.commitments.len()).as_str(),
1892 ))
1893 }
1894 }
1895}
1896
1897impl BlobsBundleV1 {
1898 pub fn new(sidecars: impl IntoIterator<Item = BlobTransactionSidecar>) -> Self {
1902 let (commitments, proofs, blobs) = sidecars.into_iter().fold(
1903 (Vec::new(), Vec::new(), Vec::new()),
1904 |(mut commitments, mut proofs, mut blobs), sidecar| {
1905 commitments.extend(sidecar.commitments);
1906 proofs.extend(sidecar.proofs);
1907 blobs.extend(sidecar.blobs);
1908 (commitments, proofs, blobs)
1909 },
1910 );
1911 Self { commitments, proofs, blobs }
1912 }
1913
1914 pub fn empty() -> Self {
1919 Self::default()
1920 }
1921
1922 pub fn versioned_hashes(&self) -> Vec<B256> {
1924 self.commitments
1925 .iter()
1926 .map(|c| alloy_eips::eip4844::kzg_to_versioned_hash(c.as_slice()))
1927 .collect()
1928 }
1929
1930 pub fn take(&mut self, len: usize) -> (Vec<Bytes48>, Vec<Bytes48>, Vec<Blob>) {
1936 (
1937 self.commitments.drain(0..len).collect(),
1938 self.proofs.drain(0..len).collect(),
1939 self.blobs.drain(0..len).collect(),
1940 )
1941 }
1942
1943 pub fn pop_sidecar(&mut self, len: usize) -> BlobTransactionSidecar {
1949 let (commitments, proofs, blobs) = self.take(len);
1950 BlobTransactionSidecar { commitments, proofs, blobs }
1951 }
1952
1953 #[cfg(feature = "kzg")]
1960 pub fn try_into_sidecar(
1961 self,
1962 ) -> Result<BlobTransactionSidecar, alloy_consensus::error::ValueError<Self>> {
1963 if self.commitments.len() != self.proofs.len() || self.commitments.len() != self.blobs.len()
1964 {
1965 return Err(alloy_consensus::error::ValueError::new(self, "length mismatch"));
1966 }
1967
1968 let Self { commitments, proofs, blobs } = self;
1969 Ok(BlobTransactionSidecar { blobs, commitments, proofs })
1970 }
1971
1972 #[cfg(feature = "kzg")]
1981 pub fn try_into_v2(self) -> Result<BlobsBundleV2, alloy_eips::eip4844::c_kzg::Error> {
1982 self.try_into_v2_with_settings(
1983 alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
1984 )
1985 }
1986
1987 #[cfg(feature = "kzg")]
1994 pub fn try_into_v2_with_settings(
1995 self,
1996 settings: &alloy_eips::eip4844::c_kzg::KzgSettings,
1997 ) -> Result<BlobsBundleV2, alloy_eips::eip4844::c_kzg::Error> {
1998 use alloy_eips::eip7594::CELLS_PER_EXT_BLOB;
1999
2000 if let [blob] = self.blobs.as_slice() {
2001 let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob.as_ckzg())?;
2002 let cell_proofs =
2003 alloy_eips::eip4844::c_kzg::KzgProof::boxed_slice_as_alloy(kzg_proofs).into();
2004 return Ok(BlobsBundleV2 {
2005 commitments: self.commitments,
2006 proofs: cell_proofs,
2007 blobs: self.blobs,
2008 });
2009 }
2010
2011 let mut cell_proofs = Vec::with_capacity(self.blobs.len() * CELLS_PER_EXT_BLOB);
2012
2013 for blob in self.blobs.iter() {
2014 let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob.as_ckzg())?;
2016 cell_proofs.extend_from_slice(alloy_eips::eip4844::c_kzg::KzgProof::slice_as_alloy(
2017 kzg_proofs.as_ref(),
2018 ));
2019 }
2020
2021 Ok(BlobsBundleV2 { commitments: self.commitments, proofs: cell_proofs, blobs: self.blobs })
2022 }
2023}
2024
2025impl From<Vec<BlobTransactionSidecar>> for BlobsBundleV1 {
2026 fn from(sidecars: Vec<BlobTransactionSidecar>) -> Self {
2027 Self::new(sidecars)
2028 }
2029}
2030
2031impl FromIterator<BlobTransactionSidecar> for BlobsBundleV1 {
2032 fn from_iter<T: IntoIterator<Item = BlobTransactionSidecar>>(iter: T) -> Self {
2033 Self::new(iter)
2034 }
2035}
2036
2037#[cfg(feature = "kzg")]
2038impl TryFrom<BlobsBundleV1> for BlobTransactionSidecar {
2039 type Error = alloy_consensus::error::ValueError<BlobsBundleV1>;
2040
2041 fn try_from(value: BlobsBundleV1) -> Result<Self, Self::Error> {
2042 value.try_into_sidecar()
2043 }
2044}
2045
2046#[cfg(feature = "kzg")]
2047impl TryFrom<BlobsBundleV1> for BlobsBundleV2 {
2048 type Error = alloy_eips::eip4844::c_kzg::Error;
2049
2050 fn try_from(value: BlobsBundleV1) -> Result<Self, Self::Error> {
2051 value.try_into_v2()
2052 }
2053}
2054
2055#[derive(Clone, Debug, Default, PartialEq, Eq)]
2057#[cfg_attr(feature = "serde", derive(serde::Serialize))]
2058#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode))]
2059#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
2060pub struct BlobsBundleV2 {
2061 pub commitments: Vec<alloy_consensus::Bytes48>,
2063 pub proofs: Vec<alloy_consensus::Bytes48>,
2065 pub blobs: Vec<alloy_consensus::Blob>,
2067}
2068
2069#[cfg(feature = "serde")]
2070impl<'de> serde::Deserialize<'de> for BlobsBundleV2 {
2071 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2072 where
2073 D: serde::Deserializer<'de>,
2074 {
2075 #[derive(serde::Deserialize)]
2076 struct BlobsBundleRaw {
2077 commitments: Vec<alloy_consensus::Bytes48>,
2078 proofs: Vec<alloy_consensus::Bytes48>,
2079 blobs: Vec<alloy_consensus::Blob>,
2080 }
2081 let raw = BlobsBundleRaw::deserialize(deserializer)?;
2082
2083 if raw.proofs.len() == raw.blobs.len() * CELLS_PER_EXT_BLOB
2084 && raw.commitments.len() == raw.blobs.len()
2085 {
2086 Ok(Self { commitments: raw.commitments, proofs: raw.proofs, blobs: raw.blobs })
2087 } else {
2088 Err(serde::de::Error::invalid_length(
2089 raw.proofs.len(),
2090 &format!("{}", raw.commitments.len() * CELLS_PER_EXT_BLOB).as_str(),
2091 ))
2092 }
2093 }
2094}
2095
2096#[cfg(feature = "ssz")]
2097impl ssz::Decode for BlobsBundleV2 {
2098 fn is_ssz_fixed_len() -> bool {
2099 false
2100 }
2101
2102 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
2103 #[derive(ssz_derive::Decode)]
2104 struct BlobsBundleRaw {
2105 commitments: Vec<alloy_consensus::Bytes48>,
2106 proofs: Vec<alloy_consensus::Bytes48>,
2107 blobs: Vec<alloy_consensus::Blob>,
2108 }
2109
2110 let raw = BlobsBundleRaw::from_ssz_bytes(bytes)?;
2111
2112 if raw.proofs.len() == raw.blobs.len() * CELLS_PER_EXT_BLOB
2113 && raw.commitments.len() == raw.blobs.len()
2114 {
2115 Ok(Self { commitments: raw.commitments, proofs: raw.proofs, blobs: raw.blobs })
2116 } else {
2117 Err(ssz::DecodeError::BytesInvalid(
2118 format!(
2119 "Invalid BlobsBundleV2: expected {} proofs and {} commitments for {} blobs, got {} proofs and {} commitments",
2120 raw.blobs.len() * CELLS_PER_EXT_BLOB,
2121 raw.blobs.len(),
2122 raw.blobs.len(),
2123 raw.proofs.len(),
2124 raw.commitments.len()
2125 )
2126 ))
2127 }
2128 }
2129}
2130
2131impl BlobsBundleV2 {
2132 pub fn new(sidecars: impl IntoIterator<Item = BlobTransactionSidecarEip7594>) -> Self {
2136 let (commitments, proofs, blobs) = sidecars.into_iter().fold(
2137 (Vec::new(), Vec::new(), Vec::new()),
2138 |(mut commitments, mut proofs, mut blobs), sidecar| {
2139 commitments.extend(sidecar.commitments);
2140 proofs.extend(sidecar.cell_proofs);
2141 blobs.extend(sidecar.blobs);
2142 (commitments, proofs, blobs)
2143 },
2144 );
2145 Self { commitments, proofs, blobs }
2146 }
2147
2148 pub fn empty() -> Self {
2153 Self::default()
2154 }
2155
2156 pub fn versioned_hashes(&self) -> Vec<B256> {
2158 self.commitments
2159 .iter()
2160 .map(|c| alloy_eips::eip4844::kzg_to_versioned_hash(c.as_slice()))
2161 .collect()
2162 }
2163
2164 pub fn take(&mut self, len: usize) -> (Vec<Bytes48>, Vec<Bytes48>, Vec<Blob>) {
2172 (
2173 self.commitments.drain(0..len).collect(),
2174 self.proofs.drain(0..len * CELLS_PER_EXT_BLOB).collect(),
2175 self.blobs.drain(0..len).collect(),
2176 )
2177 }
2178
2179 pub fn pop_sidecar(&mut self, len: usize) -> BlobTransactionSidecarEip7594 {
2185 let (commitments, cell_proofs, blobs) = self.take(len);
2186 BlobTransactionSidecarEip7594 { commitments, cell_proofs, blobs }
2187 }
2188
2189 #[cfg(feature = "kzg")]
2197 pub fn try_into_sidecar(
2198 self,
2199 ) -> Result<BlobTransactionSidecarEip7594, alloy_consensus::error::ValueError<Self>> {
2200 let expected_cell_proofs_len = self.blobs.len() * CELLS_PER_EXT_BLOB;
2201 if self.proofs.len() != expected_cell_proofs_len {
2202 let msg = format!(
2203 "cell proofs length mismatch, expected {expected_cell_proofs_len}, has {}",
2204 self.proofs.len()
2205 );
2206 return Err(alloy_consensus::error::ValueError::new(self, msg));
2207 }
2208
2209 if self.commitments.len() != self.blobs.len() {
2210 let msg = format!(
2211 "commitments length ({}) mismatch, expected blob length ({})",
2212 self.commitments.len(),
2213 self.blobs.len()
2214 );
2215 return Err(alloy_consensus::error::ValueError::new(self, msg));
2216 }
2217
2218 let Self { commitments, proofs, blobs } = self;
2219 Ok(BlobTransactionSidecarEip7594 { blobs, commitments, cell_proofs: proofs })
2220 }
2221
2222 #[cfg(feature = "kzg")]
2231 pub fn try_into_v1(self) -> Result<BlobsBundleV1, alloy_eips::eip4844::c_kzg::Error> {
2232 self.try_into_v1_with_settings(
2233 alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
2234 )
2235 }
2236
2237 #[cfg(feature = "kzg")]
2247 pub fn try_into_v1_with_settings(
2248 self,
2249 settings: &alloy_eips::eip4844::c_kzg::KzgSettings,
2250 ) -> Result<BlobsBundleV1, alloy_eips::eip4844::c_kzg::Error> {
2251 let mut proofs = Vec::with_capacity(self.blobs.len());
2252
2253 for (blob, commitment) in self.blobs.iter().zip(self.commitments.iter()) {
2254 let proof = settings.compute_blob_kzg_proof(blob.as_ckzg(), commitment.as_ckzg())?;
2256
2257 proofs.push(Bytes48::from_ckzg(proof.to_bytes()));
2258 }
2259
2260 Ok(BlobsBundleV1 { commitments: self.commitments, proofs, blobs: self.blobs })
2261 }
2262}
2263
2264impl From<Vec<BlobTransactionSidecarEip7594>> for BlobsBundleV2 {
2265 fn from(sidecars: Vec<BlobTransactionSidecarEip7594>) -> Self {
2266 Self::new(sidecars)
2267 }
2268}
2269
2270impl FromIterator<BlobTransactionSidecarEip7594> for BlobsBundleV2 {
2271 fn from_iter<T: IntoIterator<Item = BlobTransactionSidecarEip7594>>(iter: T) -> Self {
2272 Self::new(iter)
2273 }
2274}
2275
2276#[cfg(feature = "kzg")]
2277impl TryFrom<BlobsBundleV2> for BlobTransactionSidecarEip7594 {
2278 type Error = alloy_consensus::error::ValueError<BlobsBundleV2>;
2279
2280 fn try_from(value: BlobsBundleV2) -> Result<Self, Self::Error> {
2281 value.try_into_sidecar()
2282 }
2283}
2284
2285#[cfg(feature = "kzg")]
2286impl TryFrom<BlobsBundleV2> for BlobsBundleV1 {
2287 type Error = alloy_eips::eip4844::c_kzg::Error;
2288
2289 fn try_from(value: BlobsBundleV2) -> Result<Self, Self::Error> {
2290 value.try_into_v1()
2291 }
2292}
2293
2294#[derive(Clone, Debug, PartialEq, Eq)]
2301#[cfg_attr(feature = "serde", derive(serde::Serialize))]
2302#[cfg_attr(feature = "serde", serde(untagged))]
2303#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
2304pub enum ExecutionPayload {
2305 V1(ExecutionPayloadV1),
2307 V2(ExecutionPayloadV2),
2309 V3(ExecutionPayloadV3),
2311 V4(ExecutionPayloadV4),
2313}
2314
2315impl ExecutionPayload {
2316 pub fn from_block_slow<T, H>(block: &Block<T, H>) -> (Self, ExecutionPayloadSidecar)
2324 where
2325 T: Encodable2718 + Transaction,
2326 H: BlockHeader + Sealable,
2327 {
2328 Self::from_block_unchecked(block.hash_slow(), block)
2329 }
2330
2331 pub fn from_block_slow_with_bal<T, H>(
2341 block: &Block<T, H>,
2342 block_access_list: Bytes,
2343 ) -> (Self, ExecutionPayloadSidecar)
2344 where
2345 T: Encodable2718 + Transaction,
2346 H: BlockHeader + Sealable,
2347 {
2348 Self::from_block_slow_with_extras(block, block_access_list)
2349 }
2350
2351 pub fn from_block_slow_with_extras<T, H>(
2361 block: &Block<T, H>,
2362 extras: impl Into<PayloadExtras>,
2363 ) -> (Self, ExecutionPayloadSidecar)
2364 where
2365 T: Encodable2718 + Transaction,
2366 H: BlockHeader + Sealable,
2367 {
2368 let extras = extras.into();
2369 if let Some(block_access_list) = extras.bal {
2370 Self::from_block_unchecked_with_bal(block.hash_slow(), block, block_access_list)
2371 } else {
2372 Self::from_block_unchecked(block.hash_slow(), block)
2373 }
2374 }
2375
2376 pub fn from_block_unchecked<T, H>(
2392 block_hash: B256,
2393 block: &Block<T, H>,
2394 ) -> (Self, ExecutionPayloadSidecar)
2395 where
2396 T: Encodable2718 + Transaction,
2397 H: BlockHeader,
2398 {
2399 let sidecar = ExecutionPayloadSidecar::from_block(block);
2400
2401 let execution_payload = if block.header.block_access_list_hash().is_some() {
2402 Self::V4(ExecutionPayloadV4::from_block_unchecked(block_hash, block))
2404 } else if block.header.parent_beacon_block_root().is_some() {
2405 Self::V3(ExecutionPayloadV3::from_block_unchecked(block_hash, block))
2407 } else if block.body.withdrawals.is_some() {
2408 Self::V2(ExecutionPayloadV2::from_block_unchecked(block_hash, block))
2410 } else {
2411 Self::V1(ExecutionPayloadV1::from_block_unchecked(block_hash, block))
2413 };
2414
2415 (execution_payload, sidecar)
2416 }
2417
2418 pub fn from_block_unchecked_with_bal<T, H>(
2426 block_hash: B256,
2427 block: &Block<T, H>,
2428 block_access_list: Bytes,
2429 ) -> (Self, ExecutionPayloadSidecar)
2430 where
2431 T: Encodable2718 + Transaction,
2432 H: BlockHeader,
2433 {
2434 let sidecar = ExecutionPayloadSidecar::from_block(block);
2435
2436 let execution_payload = if block.header.block_access_list_hash().is_some() {
2437 Self::V4(ExecutionPayloadV4::from_block_unchecked_with_bal(
2439 block_hash,
2440 block,
2441 block_access_list,
2442 ))
2443 } else if block.header.parent_beacon_block_root().is_some() {
2444 Self::V3(ExecutionPayloadV3::from_block_unchecked(block_hash, block))
2446 } else if block.body.withdrawals.is_some() {
2447 Self::V2(ExecutionPayloadV2::from_block_unchecked(block_hash, block))
2449 } else {
2450 Self::V1(ExecutionPayloadV1::from_block_unchecked(block_hash, block))
2452 };
2453
2454 (execution_payload, sidecar)
2455 }
2456
2457 pub fn from_block_unchecked_with_extras<T, H>(
2465 block_hash: B256,
2466 block: &Block<T, H>,
2467 extras: impl Into<PayloadExtras>,
2468 ) -> (Self, ExecutionPayloadSidecar)
2469 where
2470 T: Encodable2718 + Transaction,
2471 H: BlockHeader,
2472 {
2473 let extras = extras.into();
2474 if let Some(block_access_list) = extras.bal {
2475 Self::from_block_unchecked_with_bal(block_hash, block, block_access_list)
2476 } else {
2477 Self::from_block_unchecked(block_hash, block)
2478 }
2479 }
2480
2481 pub fn try_into_block_with_sidecar<T: Decodable2718>(
2492 self,
2493 sidecar: &ExecutionPayloadSidecar,
2494 ) -> Result<Block<T>, PayloadError> {
2495 self.try_into_block_with_sidecar_with(sidecar, |tx| {
2496 T::decode_2718_exact(tx.as_ref())
2497 .map_err(alloy_rlp::Error::from)
2498 .map_err(PayloadError::from)
2499 })
2500 }
2501
2502 pub fn try_into_block_with_sidecar_with<T, F, E>(
2508 self,
2509 sidecar: &ExecutionPayloadSidecar,
2510 f: F,
2511 ) -> Result<Block<T>, PayloadError>
2512 where
2513 F: FnMut(Bytes) -> Result<T, E>,
2514 E: Into<PayloadError>,
2515 {
2516 self.into_block_with_sidecar_raw(sidecar)?.try_map_transactions(f).map_err(Into::into)
2517 }
2518
2519 pub fn into_block_with_sidecar_raw(
2524 self,
2525 sidecar: &ExecutionPayloadSidecar,
2526 ) -> Result<Block<Bytes>, PayloadError> {
2527 let mut base_block = self.into_block_raw()?;
2528 base_block.header.parent_beacon_block_root = sidecar.parent_beacon_block_root();
2529 base_block.header.requests_hash = sidecar.requests_hash();
2530 Ok(base_block)
2531 }
2532
2533 pub fn try_into_block<T: Decodable2718>(self) -> Result<Block<T>, PayloadError> {
2545 self.try_into_block_with(|tx| {
2546 T::decode_2718_exact(tx.as_ref())
2547 .map_err(alloy_rlp::Error::from)
2548 .map_err(PayloadError::from)
2549 })
2550 }
2551
2552 pub fn try_into_block_with<T, F, E>(self, f: F) -> Result<Block<T>, PayloadError>
2561 where
2562 F: FnMut(Bytes) -> Result<T, E>,
2563 E: Into<PayloadError>,
2564 {
2565 self.into_block_raw()?.try_map_transactions(f).map_err(Into::into)
2566 }
2567
2568 pub fn into_block_raw(self) -> Result<Block<Bytes>, PayloadError> {
2573 self.into_block_raw_with_transactions_root_opt(None)
2574 }
2575
2576 pub fn into_block_raw_with_transactions_root(
2581 self,
2582 transactions_root: B256,
2583 ) -> Result<Block<Bytes>, PayloadError> {
2584 self.into_block_raw_with_transactions_root_opt(Some(transactions_root))
2585 }
2586
2587 pub fn into_block_raw_with_transactions_root_opt(
2592 self,
2593 transactions_root: Option<B256>,
2594 ) -> Result<Block<Bytes>, PayloadError> {
2595 match self {
2596 Self::V1(payload) => {
2597 payload.into_block_raw_with_transactions_root_opt(transactions_root)
2598 }
2599 Self::V2(payload) => {
2600 payload.into_block_raw_with_transactions_root_opt(transactions_root)
2601 }
2602 Self::V3(payload) => {
2603 payload.into_block_raw_with_transactions_root_opt(transactions_root)
2604 }
2605 Self::V4(payload) => {
2606 payload.into_block_raw_with_transactions_root_opt(transactions_root)
2607 }
2608 }
2609 }
2610
2611 pub fn into_block_with_sidecar_raw_with_transactions_root(
2616 self,
2617 sidecar: &ExecutionPayloadSidecar,
2618 transactions_root: B256,
2619 ) -> Result<Block<Bytes>, PayloadError> {
2620 let mut base_block = self.into_block_raw_with_transactions_root(transactions_root)?;
2621 base_block.header.parent_beacon_block_root = sidecar.parent_beacon_block_root();
2622 base_block.header.requests_hash = sidecar.requests_hash();
2623 Ok(base_block)
2624 }
2625
2626 pub const fn as_v1(&self) -> &ExecutionPayloadV1 {
2628 match self {
2629 Self::V1(payload) => payload,
2630 Self::V2(payload) => &payload.payload_inner,
2631 Self::V3(payload) => &payload.payload_inner.payload_inner,
2632 Self::V4(payload) => &payload.payload_inner.payload_inner.payload_inner,
2633 }
2634 }
2635
2636 pub const fn as_v1_mut(&mut self) -> &mut ExecutionPayloadV1 {
2638 match self {
2639 Self::V1(payload) => payload,
2640 Self::V2(payload) => &mut payload.payload_inner,
2641 Self::V3(payload) => &mut payload.payload_inner.payload_inner,
2642 Self::V4(payload) => &mut payload.payload_inner.payload_inner.payload_inner,
2643 }
2644 }
2645
2646 pub fn into_v1(self) -> ExecutionPayloadV1 {
2648 match self {
2649 Self::V1(payload) => payload,
2650 Self::V2(payload) => payload.payload_inner,
2651 Self::V3(payload) => payload.payload_inner.payload_inner,
2652 Self::V4(payload) => payload.payload_inner.payload_inner.payload_inner,
2653 }
2654 }
2655
2656 pub const fn as_v2(&self) -> Option<&ExecutionPayloadV2> {
2658 match self {
2659 Self::V1(_) => None,
2660 Self::V2(payload) => Some(payload),
2661 Self::V3(payload) => Some(&payload.payload_inner),
2662 Self::V4(payload) => Some(&payload.payload_inner.payload_inner),
2663 }
2664 }
2665
2666 pub const fn as_v2_mut(&mut self) -> Option<&mut ExecutionPayloadV2> {
2668 match self {
2669 Self::V1(_) => None,
2670 Self::V2(payload) => Some(payload),
2671 Self::V3(payload) => Some(&mut payload.payload_inner),
2672 Self::V4(payload) => Some(&mut payload.payload_inner.payload_inner),
2673 }
2674 }
2675
2676 pub const fn as_v3(&self) -> Option<&ExecutionPayloadV3> {
2678 match self {
2679 Self::V1(_) | Self::V2(_) => None,
2680 Self::V3(payload) => Some(payload),
2681 Self::V4(payload) => Some(&payload.payload_inner),
2682 }
2683 }
2684
2685 pub const fn as_v3_mut(&mut self) -> Option<&mut ExecutionPayloadV3> {
2687 match self {
2688 Self::V1(_) | Self::V2(_) => None,
2689 Self::V3(payload) => Some(payload),
2690 Self::V4(payload) => Some(&mut payload.payload_inner),
2691 }
2692 }
2693
2694 pub const fn as_v4(&self) -> Option<&ExecutionPayloadV4> {
2696 match self {
2697 Self::V1(_) | Self::V2(_) | Self::V3(_) => None,
2698 Self::V4(payload) => Some(payload),
2699 }
2700 }
2701
2702 pub const fn as_v4_mut(&mut self) -> Option<&mut ExecutionPayloadV4> {
2704 match self {
2705 Self::V1(_) | Self::V2(_) | Self::V3(_) => None,
2706 Self::V4(payload) => Some(payload),
2707 }
2708 }
2709
2710 pub const fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
2712 match self.as_v2() {
2713 Some(payload) => Some(&payload.withdrawals),
2714 None => None,
2715 }
2716 }
2717
2718 pub const fn transactions(&self) -> &Vec<Bytes> {
2720 &self.as_v1().transactions
2721 }
2722
2723 pub const fn transactions_mut(&mut self) -> &mut Vec<Bytes> {
2725 &mut self.as_v1_mut().transactions
2726 }
2727
2728 pub fn header_info(&self) -> HeaderInfo {
2730 HeaderInfo {
2731 number: self.block_number(),
2732 beneficiary: self.fee_recipient(),
2733 timestamp: self.timestamp(),
2734 gas_limit: self.gas_limit(),
2735 base_fee_per_gas: Some(self.saturated_base_fee_per_gas()),
2736 excess_blob_gas: self.excess_blob_gas(),
2737 blob_gas_used: self.blob_gas_used(),
2738 difficulty: U256::ZERO,
2739 mix_hash: Some(self.prev_randao()),
2740 slot_number: self.as_v4().map(|payload| payload.slot_number),
2741 }
2742 }
2743
2744 pub fn saturated_base_fee_per_gas(&self) -> u64 {
2748 self.as_v1().base_fee_per_gas.saturating_to()
2749 }
2750
2751 pub fn blob_gas_used(&self) -> Option<u64> {
2753 self.as_v3().map(|payload| payload.blob_gas_used)
2754 }
2755
2756 pub fn excess_blob_gas(&self) -> Option<u64> {
2758 self.as_v3().map(|payload| payload.excess_blob_gas)
2759 }
2760
2761 pub fn block_access_list(&self) -> Option<&Bytes> {
2765 self.as_v4().map(|payload| &payload.block_access_list)
2766 }
2767
2768 pub fn bal_hash(&self) -> Option<B256> {
2772 self.as_v4().map(|payload| keccak256(&payload.block_access_list))
2773 }
2774
2775 pub fn slot_number(&self) -> Option<u64> {
2779 self.as_v4().map(|payload| payload.slot_number)
2780 }
2781
2782 pub const fn gas_limit(&self) -> u64 {
2784 self.as_v1().gas_limit
2785 }
2786
2787 pub const fn fee_recipient(&self) -> Address {
2789 self.as_v1().fee_recipient
2790 }
2791
2792 pub const fn timestamp(&self) -> u64 {
2794 self.as_v1().timestamp
2795 }
2796
2797 pub const fn parent_hash(&self) -> B256 {
2799 self.as_v1().parent_hash
2800 }
2801
2802 pub const fn block_hash(&self) -> B256 {
2804 self.as_v1().block_hash
2805 }
2806
2807 pub const fn block_number(&self) -> u64 {
2809 self.as_v1().block_number
2810 }
2811
2812 pub const fn block_num_hash(&self) -> BlockNumHash {
2814 self.as_v1().block_num_hash()
2815 }
2816
2817 pub const fn prev_randao(&self) -> B256 {
2819 self.as_v1().prev_randao
2820 }
2821
2822 pub fn blob_fee(&self, blob_params: BlobParams) -> Option<u128> {
2826 Some(blob_params.calc_blob_fee(self.excess_blob_gas()?))
2827 }
2828
2829 pub fn next_block_blob_fee(&self, blob_params: BlobParams) -> Option<u128> {
2835 Some(blob_params.calc_blob_fee(self.next_block_excess_blob_gas(blob_params)?))
2836 }
2837
2838 pub fn next_block_base_fee(&self, base_fee_params: BaseFeeParams) -> Option<u64> {
2842 self.as_v1().next_block_base_fee(base_fee_params)
2843 }
2844
2845 pub fn next_block_excess_blob_gas(&self, blob_params: BlobParams) -> Option<u64> {
2850 Some(blob_params.next_block_excess_blob_gas_osaka(
2851 self.excess_blob_gas()?,
2852 self.blob_gas_used()?,
2853 self.as_v1().base_fee_per_gas.to(),
2854 ))
2855 }
2856
2857 pub fn maybe_next_block_excess_blob_gas(&self, blob_params: Option<BlobParams>) -> Option<u64> {
2862 self.next_block_excess_blob_gas(blob_params?)
2863 }
2864
2865 pub fn decoded_transactions<T: Decodable2718>(
2869 &self,
2870 ) -> impl Iterator<Item = Eip2718Result<T>> + '_ {
2871 self.transactions().iter().map(|tx_bytes| T::decode_2718_exact(tx_bytes.as_ref()))
2872 }
2873
2874 pub fn decoded_transactions_with_encoded<T: Decodable2718>(
2878 &self,
2879 ) -> impl Iterator<Item = Eip2718Result<WithEncoded<T>>> + '_ {
2880 self.transactions().iter().map(|tx_bytes| {
2881 T::decode_2718_exact(tx_bytes.as_ref()).map(|tx| WithEncoded::new(tx_bytes.clone(), tx))
2882 })
2883 }
2884
2885 pub fn recovered_transactions<T>(
2889 &self,
2890 ) -> impl Iterator<
2891 Item = Result<
2892 alloy_consensus::transaction::Recovered<T>,
2893 alloy_consensus::crypto::RecoveryError,
2894 >,
2895 > + '_
2896 where
2897 T: Decodable2718 + alloy_consensus::transaction::SignerRecoverable,
2898 {
2899 self.decoded_transactions::<T>().map(|res| {
2900 res.map_err(alloy_consensus::crypto::RecoveryError::from_source)
2901 .and_then(|tx| tx.try_into_recovered())
2902 })
2903 }
2904
2905 pub fn recovered_transactions_with_encoded<T>(
2911 &self,
2912 ) -> impl Iterator<
2913 Item = Result<
2914 WithEncoded<alloy_consensus::transaction::Recovered<T>>,
2915 alloy_consensus::crypto::RecoveryError,
2916 >,
2917 > + '_
2918 where
2919 T: Decodable2718 + alloy_consensus::transaction::SignerRecoverable,
2920 {
2921 self.transactions().iter().map(|tx_bytes| {
2922 T::decode_2718_exact(tx_bytes.as_ref())
2923 .map_err(alloy_consensus::crypto::RecoveryError::from_source)
2924 .and_then(|tx| {
2925 tx.try_into_recovered()
2926 .map(|recovered| WithEncoded::new(tx_bytes.clone(), recovered))
2927 })
2928 })
2929 }
2930
2931 #[doc(hidden)]
2933 pub const fn set_parent_hash(&mut self, parent_hash: B256) {
2934 self.as_v1_mut().parent_hash = parent_hash;
2935 }
2936
2937 #[doc(hidden)]
2939 pub const fn set_fee_recipient(&mut self, fee_recipient: Address) {
2940 self.as_v1_mut().fee_recipient = fee_recipient;
2941 }
2942
2943 #[doc(hidden)]
2945 pub const fn set_state_root(&mut self, state_root: B256) {
2946 self.as_v1_mut().state_root = state_root;
2947 }
2948
2949 #[doc(hidden)]
2951 pub const fn set_receipts_root(&mut self, receipts_root: B256) {
2952 self.as_v1_mut().receipts_root = receipts_root;
2953 }
2954
2955 #[doc(hidden)]
2957 pub const fn set_logs_bloom(&mut self, logs_bloom: Bloom) {
2958 self.as_v1_mut().logs_bloom = logs_bloom;
2959 }
2960
2961 #[doc(hidden)]
2963 pub const fn set_prev_randao(&mut self, prev_randao: B256) {
2964 self.as_v1_mut().prev_randao = prev_randao;
2965 }
2966
2967 #[doc(hidden)]
2969 pub const fn set_block_number(&mut self, block_number: u64) {
2970 self.as_v1_mut().block_number = block_number;
2971 }
2972
2973 #[doc(hidden)]
2975 pub const fn set_gas_limit(&mut self, gas_limit: u64) {
2976 self.as_v1_mut().gas_limit = gas_limit;
2977 }
2978
2979 #[doc(hidden)]
2981 pub const fn set_gas_used(&mut self, gas_used: u64) {
2982 self.as_v1_mut().gas_used = gas_used;
2983 }
2984
2985 #[doc(hidden)]
2987 pub const fn set_timestamp(&mut self, timestamp: u64) {
2988 self.as_v1_mut().timestamp = timestamp;
2989 }
2990
2991 #[doc(hidden)]
2993 pub fn set_extra_data(&mut self, extra_data: Bytes) {
2994 self.as_v1_mut().extra_data = extra_data;
2995 }
2996
2997 #[doc(hidden)]
2999 pub const fn set_base_fee_per_gas(&mut self, base_fee_per_gas: U256) {
3000 self.as_v1_mut().base_fee_per_gas = base_fee_per_gas;
3001 }
3002
3003 #[doc(hidden)]
3005 pub const fn set_block_hash(&mut self, block_hash: B256) {
3006 self.as_v1_mut().block_hash = block_hash;
3007 }
3008
3009 #[doc(hidden)]
3013 pub fn set_withdrawals(&mut self, withdrawals: Vec<Withdrawal>) -> bool {
3014 match self.as_v2_mut() {
3015 Some(payload) => {
3016 payload.withdrawals = withdrawals;
3017 true
3018 }
3019 None => false,
3020 }
3021 }
3022
3023 #[doc(hidden)]
3027 pub const fn set_blob_gas_used(&mut self, blob_gas_used: u64) -> bool {
3028 match self.as_v3_mut() {
3029 Some(payload) => {
3030 payload.blob_gas_used = blob_gas_used;
3031 true
3032 }
3033 None => false,
3034 }
3035 }
3036
3037 #[doc(hidden)]
3041 pub const fn set_excess_blob_gas(&mut self, excess_blob_gas: u64) -> bool {
3042 match self.as_v3_mut() {
3043 Some(payload) => {
3044 payload.excess_blob_gas = excess_blob_gas;
3045 true
3046 }
3047 None => false,
3048 }
3049 }
3050}
3051
3052impl From<ExecutionPayloadV1> for ExecutionPayload {
3053 fn from(payload: ExecutionPayloadV1) -> Self {
3054 Self::V1(payload)
3055 }
3056}
3057
3058impl From<ExecutionPayloadV2> for ExecutionPayload {
3059 fn from(payload: ExecutionPayloadV2) -> Self {
3060 Self::V2(payload)
3061 }
3062}
3063
3064impl From<ExecutionPayloadFieldV2> for ExecutionPayload {
3065 fn from(payload: ExecutionPayloadFieldV2) -> Self {
3066 payload.into_payload()
3067 }
3068}
3069
3070impl From<ExecutionPayloadV3> for ExecutionPayload {
3071 fn from(payload: ExecutionPayloadV3) -> Self {
3072 Self::V3(payload)
3073 }
3074}
3075
3076impl From<ExecutionPayloadV4> for ExecutionPayload {
3077 fn from(payload: ExecutionPayloadV4) -> Self {
3078 Self::V4(payload)
3079 }
3080}
3081
3082impl<T: Decodable2718> TryFrom<ExecutionPayload> for Block<T> {
3083 type Error = PayloadError;
3084
3085 fn try_from(value: ExecutionPayload) -> Result<Self, Self::Error> {
3086 value.try_into_block()
3087 }
3088}
3089
3090#[cfg(feature = "serde")]
3092impl<'de> serde::Deserialize<'de> for ExecutionPayload {
3093 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3094 where
3095 D: serde::Deserializer<'de>,
3096 {
3097 use alloy_primitives::U64;
3098
3099 struct ExecutionPayloadVisitor;
3100
3101 impl<'de> serde::de::Visitor<'de> for ExecutionPayloadVisitor {
3102 type Value = ExecutionPayload;
3103
3104 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3105 formatter.write_str("a valid ExecutionPayload object")
3106 }
3107
3108 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
3109 where
3110 A: serde::de::MapAccess<'de>,
3111 {
3112 #[cfg_attr(feature = "serde", derive(serde::Deserialize))]
3114 #[cfg_attr(feature = "serde", serde(field_identifier, rename_all = "camelCase"))]
3115 enum Fields {
3116 ParentHash,
3117 FeeRecipient,
3118 StateRoot,
3119 ReceiptsRoot,
3120 LogsBloom,
3121 PrevRandao,
3122 BlockNumber,
3123 GasLimit,
3124 GasUsed,
3125 Timestamp,
3126 ExtraData,
3127 BaseFeePerGas,
3128 BlockHash,
3129 Transactions,
3130 Withdrawals,
3132 BlobGasUsed,
3134 ExcessBlobGas,
3135 BlockAccessList,
3137 SlotNumber,
3138 }
3139
3140 let mut parent_hash = None;
3141 let mut fee_recipient = None;
3142 let mut state_root = None;
3143 let mut receipts_root = None;
3144 let mut logs_bloom = None;
3145 let mut prev_randao = None;
3146 let mut block_number = None;
3147 let mut gas_limit = None;
3148 let mut gas_used = None;
3149 let mut timestamp = None;
3150 let mut extra_data = None;
3151 let mut base_fee_per_gas = None;
3152 let mut block_hash = None;
3153 let mut transactions = None;
3154 let mut withdrawals = None;
3155 let mut blob_gas_used = None;
3156 let mut excess_blob_gas = None;
3157 let mut block_access_list = None;
3158 let mut slot_number = None;
3159
3160 while let Some(key) = map.next_key()? {
3161 match key {
3162 Fields::ParentHash => parent_hash = Some(map.next_value()?),
3163 Fields::FeeRecipient => fee_recipient = Some(map.next_value()?),
3164 Fields::StateRoot => state_root = Some(map.next_value()?),
3165 Fields::ReceiptsRoot => receipts_root = Some(map.next_value()?),
3166 Fields::LogsBloom => logs_bloom = Some(map.next_value()?),
3167 Fields::PrevRandao => prev_randao = Some(map.next_value()?),
3168 Fields::BlockNumber => {
3169 let raw = map.next_value::<U64>()?;
3170 block_number = Some(raw.to());
3171 }
3172 Fields::GasLimit => {
3173 let raw = map.next_value::<U64>()?;
3174 gas_limit = Some(raw.to());
3175 }
3176 Fields::GasUsed => {
3177 let raw = map.next_value::<U64>()?;
3178 gas_used = Some(raw.to());
3179 }
3180 Fields::Timestamp => {
3181 let raw = map.next_value::<U64>()?;
3182 timestamp = Some(raw.to());
3183 }
3184 Fields::ExtraData => extra_data = Some(map.next_value()?),
3185 Fields::BaseFeePerGas => base_fee_per_gas = Some(map.next_value()?),
3186 Fields::BlockHash => block_hash = Some(map.next_value()?),
3187 Fields::Transactions => transactions = Some(map.next_value()?),
3188 Fields::Withdrawals => withdrawals = Some(map.next_value()?),
3189 Fields::BlobGasUsed => {
3190 let raw = map.next_value::<U64>()?;
3191 blob_gas_used = Some(raw.to());
3192 }
3193 Fields::ExcessBlobGas => {
3194 let raw = map.next_value::<U64>()?;
3195 excess_blob_gas = Some(raw.to());
3196 }
3197 Fields::BlockAccessList => {
3198 block_access_list = Some(map.next_value()?);
3199 }
3200 Fields::SlotNumber => {
3201 let raw = map.next_value::<U64>()?;
3202 slot_number = Some(raw.to());
3203 }
3204 }
3205 }
3206
3207 let parent_hash =
3208 parent_hash.ok_or_else(|| serde::de::Error::missing_field("parentHash"))?;
3209 let fee_recipient =
3210 fee_recipient.ok_or_else(|| serde::de::Error::missing_field("feeRecipient"))?;
3211 let state_root =
3212 state_root.ok_or_else(|| serde::de::Error::missing_field("stateRoot"))?;
3213 let receipts_root =
3214 receipts_root.ok_or_else(|| serde::de::Error::missing_field("receiptsRoot"))?;
3215 let logs_bloom =
3216 logs_bloom.ok_or_else(|| serde::de::Error::missing_field("logsBloom"))?;
3217 let prev_randao =
3218 prev_randao.ok_or_else(|| serde::de::Error::missing_field("prevRandao"))?;
3219 let block_number =
3220 block_number.ok_or_else(|| serde::de::Error::missing_field("blockNumber"))?;
3221 let gas_limit =
3222 gas_limit.ok_or_else(|| serde::de::Error::missing_field("gasLimit"))?;
3223 let gas_used =
3224 gas_used.ok_or_else(|| serde::de::Error::missing_field("gasUsed"))?;
3225 let timestamp =
3226 timestamp.ok_or_else(|| serde::de::Error::missing_field("timestamp"))?;
3227 let extra_data =
3228 extra_data.ok_or_else(|| serde::de::Error::missing_field("extraData"))?;
3229 let base_fee_per_gas = base_fee_per_gas
3230 .ok_or_else(|| serde::de::Error::missing_field("baseFeePerGas"))?;
3231 let block_hash =
3232 block_hash.ok_or_else(|| serde::de::Error::missing_field("blockHash"))?;
3233 let transactions =
3234 transactions.ok_or_else(|| serde::de::Error::missing_field("transactions"))?;
3235
3236 let v1 = ExecutionPayloadV1 {
3237 parent_hash,
3238 fee_recipient,
3239 state_root,
3240 receipts_root,
3241 logs_bloom,
3242 prev_randao,
3243 block_number,
3244 gas_limit,
3245 gas_used,
3246 timestamp,
3247 extra_data,
3248 base_fee_per_gas,
3249 block_hash,
3250 transactions,
3251 };
3252
3253 let Some(withdrawals) = withdrawals else {
3254 return if blob_gas_used.is_none() && excess_blob_gas.is_none() {
3255 Ok(ExecutionPayload::V1(v1))
3256 } else {
3257 Err(serde::de::Error::custom("invalid enum variant"))
3258 };
3259 };
3260
3261 if let (Some(blob_gas_used), Some(excess_blob_gas)) =
3262 (blob_gas_used, excess_blob_gas)
3263 {
3264 let v3 = ExecutionPayloadV3 {
3265 payload_inner: ExecutionPayloadV2 { payload_inner: v1, withdrawals },
3266 blob_gas_used,
3267 excess_blob_gas,
3268 };
3269
3270 return match (block_access_list, slot_number) {
3272 (Some(block_access_list), Some(slot_number)) => {
3273 Ok(ExecutionPayload::V4(ExecutionPayloadV4 {
3274 payload_inner: v3,
3275 block_access_list,
3276 slot_number,
3277 }))
3278 }
3279 (None, None) => Ok(ExecutionPayload::V3(v3)),
3281 _ => Err(serde::de::Error::custom("invalid enum variant")),
3282 };
3283 }
3284
3285 if blob_gas_used.is_some() || excess_blob_gas.is_some() {
3287 return Err(serde::de::Error::custom("invalid enum variant"));
3288 }
3289
3290 if block_access_list.is_some() || slot_number.is_some() {
3292 return Err(serde::de::Error::custom("invalid enum variant"));
3293 }
3294
3295 Ok(ExecutionPayload::V2(ExecutionPayloadV2 { payload_inner: v1, withdrawals }))
3296 }
3297 }
3298
3299 const FIELDS: &[&str] = &[
3300 "parentHash",
3301 "feeRecipient",
3302 "stateRoot",
3303 "receiptsRoot",
3304 "logsBloom",
3305 "prevRandao",
3306 "blockNumber",
3307 "gasLimit",
3308 "gasUsed",
3309 "timestamp",
3310 "extraData",
3311 "baseFeePerGas",
3312 "blockHash",
3313 "transactions",
3314 "withdrawals",
3315 "blobGasUsed",
3316 "excessBlobGas",
3317 "blockAccessList",
3318 "slotNumber",
3319 ];
3320 deserializer.deserialize_struct("ExecutionPayload", FIELDS, ExecutionPayloadVisitor)
3321 }
3322}
3323
3324#[derive(Clone, Debug, PartialEq, Eq)]
3328#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3329#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
3330#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
3331pub struct ExecutionPayloadBodyV1 {
3332 pub transactions: Vec<Bytes>,
3334 pub withdrawals: Option<Vec<Withdrawal>>,
3338}
3339
3340impl ExecutionPayloadBodyV1 {
3341 pub fn new<'a, T>(
3343 withdrawals: Option<Withdrawals>,
3344 transactions: impl IntoIterator<Item = &'a T>,
3345 ) -> Self
3346 where
3347 T: Encodable2718 + 'a,
3348 {
3349 Self {
3350 transactions: transactions.into_iter().map(|tx| tx.encoded_2718().into()).collect(),
3351 withdrawals: withdrawals.map(Withdrawals::into_inner),
3352 }
3353 }
3354
3355 pub fn from_block<T: Encodable2718, H>(block: Block<T, H>) -> Self {
3357 let BlockBody { withdrawals, transactions, .. } = block.into_body();
3358 Self::new(withdrawals, transactions.iter())
3359 }
3360}
3361
3362impl<T: Encodable2718, H> From<Block<T, H>> for ExecutionPayloadBodyV1 {
3363 fn from(value: Block<T, H>) -> Self {
3364 Self::from_block(value)
3365 }
3366}
3367
3368#[derive(Clone, Debug, PartialEq, Eq)]
3374#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3375#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
3376#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
3377#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
3378pub struct ExecutionPayloadBodyV2 {
3379 pub transactions: Vec<Bytes>,
3381 pub withdrawals: Option<Vec<Withdrawal>>,
3385 pub block_access_list: Option<Bytes>,
3389}
3390
3391impl ExecutionPayloadBodyV2 {
3392 pub fn new<'a, T>(
3395 withdrawals: Option<Withdrawals>,
3396 transactions: impl IntoIterator<Item = &'a T>,
3397 block_access_list: Option<Bytes>,
3398 ) -> Self
3399 where
3400 T: Encodable2718 + 'a,
3401 {
3402 Self {
3403 transactions: transactions.into_iter().map(|tx| tx.encoded_2718().into()).collect(),
3404 withdrawals: withdrawals.map(Withdrawals::into_inner),
3405 block_access_list,
3406 }
3407 }
3408
3409 pub fn from_block<T: Encodable2718, H>(
3412 block: Block<T, H>,
3413 block_access_list: Option<Bytes>,
3414 ) -> Self {
3415 let BlockBody { withdrawals, transactions, .. } = block.into_body();
3416 Self::new(withdrawals, transactions.iter(), block_access_list)
3417 }
3418}
3419
3420impl From<ExecutionPayloadBodyV1> for ExecutionPayloadBodyV2 {
3421 fn from(v1: ExecutionPayloadBodyV1) -> Self {
3422 Self { transactions: v1.transactions, withdrawals: v1.withdrawals, block_access_list: None }
3423 }
3424}
3425
3426impl From<ExecutionPayloadBodyV2> for ExecutionPayloadBodyV1 {
3427 fn from(v2: ExecutionPayloadBodyV2) -> Self {
3428 Self { transactions: v2.transactions, withdrawals: v2.withdrawals }
3429 }
3430}
3431
3432#[derive(Clone, Debug, Default, PartialEq, Eq)]
3435#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3436#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
3437#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
3438pub struct PayloadAttributes {
3439 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
3441 pub timestamp: u64,
3442 pub prev_randao: B256,
3444 pub suggested_fee_recipient: Address,
3446 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
3449 pub withdrawals: Option<Vec<Withdrawal>>,
3450 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
3454 pub parent_beacon_block_root: Option<B256>,
3455 #[cfg_attr(
3459 feature = "serde",
3460 serde(
3461 default,
3462 skip_serializing_if = "Option::is_none",
3463 with = "alloy_serde::quantity::opt"
3464 )
3465 )]
3466 pub slot_number: Option<u64>,
3467 #[cfg_attr(
3471 feature = "serde",
3472 serde(
3473 default,
3474 skip_serializing_if = "Option::is_none",
3475 with = "alloy_serde::quantity::opt"
3476 )
3477 )]
3478 pub target_gas_limit: Option<u64>,
3479}
3480
3481impl PayloadAttributes {
3482 pub const fn with_timestamp(mut self, timestamp: u64) -> Self {
3484 self.timestamp = timestamp;
3485 self
3486 }
3487
3488 pub fn with_withdrawals(mut self, withdrawals: Vec<Withdrawal>) -> Self {
3490 self.withdrawals = Some(withdrawals);
3491 self
3492 }
3493
3494 pub const fn with_parent_beacon_block_root(mut self, parent_beacon_block_root: B256) -> Self {
3496 self.parent_beacon_block_root = Some(parent_beacon_block_root);
3497 self
3498 }
3499
3500 pub const fn with_slot_number(mut self, slot_number: u64) -> Self {
3502 self.slot_number = Some(slot_number);
3503 self
3504 }
3505}
3506
3507#[cfg(feature = "ssz")]
3508impl PayloadAttributes {
3509 fn ssz_v1_fixed_len() -> usize {
3510 <u64 as ssz::Encode>::ssz_fixed_len()
3511 + <B256 as ssz::Encode>::ssz_fixed_len()
3512 + <Address as ssz::Encode>::ssz_fixed_len()
3513 }
3514
3515 fn ssz_v2_fixed_len() -> usize {
3516 Self::ssz_v1_fixed_len() + <Vec<Withdrawal> as ssz::Encode>::ssz_fixed_len()
3517 }
3518
3519 fn ssz_v3_fixed_len() -> usize {
3520 Self::ssz_v2_fixed_len() + <B256 as ssz::Encode>::ssz_fixed_len()
3521 }
3522
3523 fn ssz_v4_slot_fixed_len() -> usize {
3524 Self::ssz_v3_fixed_len() + <u64 as ssz::Encode>::ssz_fixed_len()
3525 }
3526
3527 fn ssz_v4_target_fixed_len() -> usize {
3528 Self::ssz_v4_slot_fixed_len() + <u64 as ssz::Encode>::ssz_fixed_len()
3529 }
3530
3531 fn ssz_fixed_section_len(&self) -> usize {
3532 if self.target_gas_limit.is_some() {
3533 Self::ssz_v4_target_fixed_len()
3534 } else if self.slot_number.is_some() {
3535 Self::ssz_v4_slot_fixed_len()
3536 } else if self.parent_beacon_block_root.is_some() {
3537 Self::ssz_v3_fixed_len()
3538 } else if self.withdrawals.is_some() {
3539 Self::ssz_v2_fixed_len()
3540 } else {
3541 Self::ssz_v1_fixed_len()
3542 }
3543 }
3544}
3545
3546#[cfg(feature = "ssz")]
3547impl ssz::Encode for PayloadAttributes {
3548 fn is_ssz_fixed_len() -> bool {
3549 false
3550 }
3551
3552 fn ssz_append(&self, buf: &mut Vec<u8>) {
3553 let fixed_section_len = self.ssz_fixed_section_len();
3554 let mut encoder = ssz::SszEncoder::container(buf, fixed_section_len);
3555
3556 encoder.append(&self.timestamp);
3557 encoder.append(&self.prev_randao);
3558 encoder.append(&self.suggested_fee_recipient);
3559
3560 if fixed_section_len >= Self::ssz_v2_fixed_len() {
3561 let empty_withdrawals = Vec::new();
3562 let withdrawals = self.withdrawals.as_ref().unwrap_or(&empty_withdrawals);
3563 encoder.append(withdrawals);
3564 }
3565
3566 if fixed_section_len >= Self::ssz_v3_fixed_len() {
3567 encoder.append(&self.parent_beacon_block_root.unwrap_or_default());
3568 }
3569
3570 if fixed_section_len >= Self::ssz_v4_slot_fixed_len() {
3571 encoder.append(&self.slot_number.unwrap_or_default());
3572 }
3573
3574 if fixed_section_len == Self::ssz_v4_target_fixed_len() {
3575 encoder.append(&self.target_gas_limit.unwrap_or_default());
3576 }
3577
3578 encoder.finalize();
3579 }
3580
3581 fn ssz_bytes_len(&self) -> usize {
3582 let fixed_section_len = self.ssz_fixed_section_len();
3583 let withdrawals_len = if fixed_section_len >= Self::ssz_v2_fixed_len() {
3584 self.withdrawals.as_ref().map(ssz::Encode::ssz_bytes_len).unwrap_or_default()
3585 } else {
3586 0
3587 };
3588
3589 fixed_section_len + withdrawals_len
3590 }
3591}
3592
3593#[cfg(feature = "ssz")]
3594impl ssz::Decode for PayloadAttributes {
3595 fn is_ssz_fixed_len() -> bool {
3596 false
3597 }
3598
3599 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
3600 if bytes.len() == Self::ssz_v1_fixed_len() {
3601 let mut builder = ssz::SszDecoderBuilder::new(bytes);
3602
3603 builder.register_type::<u64>()?;
3604 builder.register_type::<B256>()?;
3605 builder.register_type::<Address>()?;
3606
3607 let mut decoder = builder.build()?;
3608
3609 return Ok(Self {
3610 timestamp: decoder.decode_next()?,
3611 prev_randao: decoder.decode_next()?,
3612 suggested_fee_recipient: decoder.decode_next()?,
3613 withdrawals: None,
3614 parent_beacon_block_root: None,
3615 slot_number: None,
3616 target_gas_limit: None,
3617 });
3618 }
3619
3620 if bytes.len() < Self::ssz_v2_fixed_len() {
3621 return Err(ssz::DecodeError::InvalidByteLength {
3622 len: bytes.len(),
3623 expected: Self::ssz_v2_fixed_len(),
3624 });
3625 }
3626
3627 let offset = u32::from_le_bytes([
3628 bytes[Self::ssz_v1_fixed_len()],
3629 bytes[Self::ssz_v1_fixed_len() + 1],
3630 bytes[Self::ssz_v1_fixed_len() + 2],
3631 bytes[Self::ssz_v1_fixed_len() + 3],
3632 ]) as usize;
3633
3634 let mut builder = ssz::SszDecoderBuilder::new(bytes);
3635
3636 builder.register_type::<u64>()?;
3637 builder.register_type::<B256>()?;
3638 builder.register_type::<Address>()?;
3639 builder.register_type::<Vec<Withdrawal>>()?;
3640
3641 match offset {
3642 offset if offset == Self::ssz_v2_fixed_len() => {
3643 let mut decoder = builder.build()?;
3644
3645 Ok(Self {
3646 timestamp: decoder.decode_next()?,
3647 prev_randao: decoder.decode_next()?,
3648 suggested_fee_recipient: decoder.decode_next()?,
3649 withdrawals: Some(decoder.decode_next()?),
3650 parent_beacon_block_root: None,
3651 slot_number: None,
3652 target_gas_limit: None,
3653 })
3654 }
3655 offset if offset == Self::ssz_v3_fixed_len() => {
3656 builder.register_type::<B256>()?;
3657 let mut decoder = builder.build()?;
3658
3659 Ok(Self {
3660 timestamp: decoder.decode_next()?,
3661 prev_randao: decoder.decode_next()?,
3662 suggested_fee_recipient: decoder.decode_next()?,
3663 withdrawals: Some(decoder.decode_next()?),
3664 parent_beacon_block_root: Some(decoder.decode_next()?),
3665 slot_number: None,
3666 target_gas_limit: None,
3667 })
3668 }
3669 offset if offset == Self::ssz_v4_slot_fixed_len() => {
3670 builder.register_type::<B256>()?;
3671 builder.register_type::<u64>()?;
3672 let mut decoder = builder.build()?;
3673
3674 Ok(Self {
3675 timestamp: decoder.decode_next()?,
3676 prev_randao: decoder.decode_next()?,
3677 suggested_fee_recipient: decoder.decode_next()?,
3678 withdrawals: Some(decoder.decode_next()?),
3679 parent_beacon_block_root: Some(decoder.decode_next()?),
3680 slot_number: Some(decoder.decode_next()?),
3681 target_gas_limit: None,
3682 })
3683 }
3684 offset if offset == Self::ssz_v4_target_fixed_len() => {
3685 builder.register_type::<B256>()?;
3686 builder.register_type::<u64>()?;
3687 builder.register_type::<u64>()?;
3688 let mut decoder = builder.build()?;
3689
3690 Ok(Self {
3691 timestamp: decoder.decode_next()?,
3692 prev_randao: decoder.decode_next()?,
3693 suggested_fee_recipient: decoder.decode_next()?,
3694 withdrawals: Some(decoder.decode_next()?),
3695 parent_beacon_block_root: Some(decoder.decode_next()?),
3696 slot_number: Some(decoder.decode_next()?),
3697 target_gas_limit: Some(decoder.decode_next()?),
3698 })
3699 }
3700 offset => Err(ssz::DecodeError::BytesInvalid(format!(
3701 "invalid PayloadAttributes SSZ fixed section offset: {offset}"
3702 ))),
3703 }
3704 }
3705}
3706
3707#[derive(Clone, Debug, PartialEq, Eq)]
3709#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
3710#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
3711#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
3712pub struct PayloadStatus {
3713 #[cfg_attr(feature = "serde", serde(flatten))]
3715 pub status: PayloadStatusEnum,
3716 pub latest_valid_hash: Option<B256>,
3718}
3719
3720impl PayloadStatus {
3721 pub const fn new(status: PayloadStatusEnum, latest_valid_hash: Option<B256>) -> Self {
3723 Self { status, latest_valid_hash }
3724 }
3725
3726 pub const fn from_status(status: PayloadStatusEnum) -> Self {
3728 Self { status, latest_valid_hash: None }
3729 }
3730
3731 pub const fn with_latest_valid_hash(mut self, latest_valid_hash: B256) -> Self {
3733 self.latest_valid_hash = Some(latest_valid_hash);
3734 self
3735 }
3736
3737 pub const fn maybe_latest_valid_hash(mut self, latest_valid_hash: Option<B256>) -> Self {
3739 self.latest_valid_hash = latest_valid_hash;
3740 self
3741 }
3742
3743 pub const fn is_syncing(&self) -> bool {
3745 self.status.is_syncing()
3746 }
3747
3748 pub const fn is_valid(&self) -> bool {
3750 self.status.is_valid()
3751 }
3752
3753 pub const fn is_invalid(&self) -> bool {
3755 self.status.is_invalid()
3756 }
3757}
3758
3759#[cfg(feature = "ssz")]
3760impl ssz::Encode for PayloadStatus {
3761 fn is_ssz_fixed_len() -> bool {
3762 false
3763 }
3764
3765 fn ssz_append(&self, buf: &mut Vec<u8>) {
3766 let validation_error =
3767 self.status.validation_error().map(str::as_bytes).unwrap_or_default().to_vec();
3768 let latest_valid_hash = self.latest_valid_hash.unwrap_or_default();
3769 let offset = <u8 as ssz::Encode>::ssz_fixed_len()
3770 + <B256 as ssz::Encode>::ssz_fixed_len()
3771 + <Vec<u8> as ssz::Encode>::ssz_fixed_len();
3772 let mut encoder = ssz::SszEncoder::container(buf, offset);
3773
3774 encoder.append(&self.status.ssz_code());
3775 encoder.append(&latest_valid_hash);
3776 encoder.append(&validation_error);
3777
3778 encoder.finalize();
3779 }
3780
3781 fn ssz_bytes_len(&self) -> usize {
3782 let validation_error_len = self.status.validation_error().map(str::len).unwrap_or_default();
3783 <u8 as ssz::Encode>::ssz_fixed_len()
3784 + <B256 as ssz::Encode>::ssz_fixed_len()
3785 + <Vec<u8> as ssz::Encode>::ssz_fixed_len()
3786 + validation_error_len
3787 }
3788}
3789
3790#[cfg(feature = "ssz")]
3791impl ssz::Decode for PayloadStatus {
3792 fn is_ssz_fixed_len() -> bool {
3793 false
3794 }
3795
3796 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
3797 let mut builder = ssz::SszDecoderBuilder::new(bytes);
3798
3799 builder.register_type::<u8>()?;
3800 builder.register_type::<B256>()?;
3801 builder.register_type::<Vec<u8>>()?;
3802
3803 let mut decoder = builder.build()?;
3804 let status_code: u8 = decoder.decode_next()?;
3805 let latest_valid_hash: B256 = decoder.decode_next()?;
3806 let validation_error: Vec<u8> = decoder.decode_next()?;
3807
3808 let status = PayloadStatusEnum::from_ssz_code(status_code, validation_error)?;
3809 let latest_valid_hash = (!latest_valid_hash.is_zero()).then_some(latest_valid_hash);
3810
3811 Ok(Self { status, latest_valid_hash })
3812 }
3813}
3814
3815impl core::fmt::Display for PayloadStatus {
3816 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3817 write!(
3818 f,
3819 "PayloadStatus {{ status: {}, latestValidHash: {:?} }}",
3820 self.status, self.latest_valid_hash
3821 )
3822 }
3823}
3824
3825#[cfg(feature = "serde")]
3826impl serde::Serialize for PayloadStatus {
3827 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3828 where
3829 S: serde::Serializer,
3830 {
3831 use serde::ser::SerializeMap;
3832 let mut map = serializer.serialize_map(Some(3))?;
3833 map.serialize_entry("status", self.status.as_str())?;
3834 map.serialize_entry("latestValidHash", &self.latest_valid_hash)?;
3835 map.serialize_entry("validationError", &self.status.validation_error())?;
3836 map.end()
3837 }
3838}
3839
3840impl From<PayloadError> for PayloadStatusEnum {
3841 fn from(error: PayloadError) -> Self {
3842 Self::Invalid { validation_error: error.to_string() }
3843 }
3844}
3845
3846#[derive(Clone, Debug, PartialEq, Eq)]
3848#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3849#[cfg_attr(feature = "serde", serde(tag = "status", rename_all = "SCREAMING_SNAKE_CASE"))]
3850#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
3851pub enum PayloadStatusEnum {
3852 Valid,
3856
3857 Invalid {
3861 #[cfg_attr(feature = "serde", serde(rename = "validationError"))]
3863 validation_error: String,
3864 },
3865
3866 Syncing,
3870
3871 Accepted,
3874}
3875
3876impl PayloadStatusEnum {
3877 pub const fn as_str(&self) -> &'static str {
3879 match self {
3880 Self::Valid => "VALID",
3881 Self::Invalid { .. } => "INVALID",
3882 Self::Syncing => "SYNCING",
3883 Self::Accepted => "ACCEPTED",
3884 }
3885 }
3886
3887 pub fn validation_error(&self) -> Option<&str> {
3889 match self {
3890 Self::Invalid { validation_error } => Some(validation_error),
3891 _ => None,
3892 }
3893 }
3894
3895 pub const fn is_syncing(&self) -> bool {
3897 matches!(self, Self::Syncing)
3898 }
3899
3900 pub const fn is_valid(&self) -> bool {
3902 matches!(self, Self::Valid)
3903 }
3904
3905 pub const fn is_invalid(&self) -> bool {
3907 matches!(self, Self::Invalid { .. })
3908 }
3909
3910 #[cfg(feature = "ssz")]
3911 const fn ssz_code(&self) -> u8 {
3912 match self {
3913 Self::Valid => 0,
3914 Self::Invalid { .. } => 1,
3915 Self::Syncing => 2,
3916 Self::Accepted => 3,
3917 }
3918 }
3919
3920 #[cfg(feature = "ssz")]
3921 fn from_ssz_code(status_code: u8, validation_error: Vec<u8>) -> Result<Self, ssz::DecodeError> {
3922 match status_code {
3923 0 => {
3924 if !validation_error.is_empty() {
3925 return Err(ssz::DecodeError::BytesInvalid(
3926 "unexpected validation error for VALID status".to_string(),
3927 ));
3928 }
3929 Ok(Self::Valid)
3930 }
3931 1 => String::from_utf8(validation_error)
3932 .map(|validation_error| Self::Invalid { validation_error })
3933 .map_err(|err| ssz::DecodeError::BytesInvalid(err.to_string())),
3934 2 => {
3935 if !validation_error.is_empty() {
3936 return Err(ssz::DecodeError::BytesInvalid(
3937 "unexpected validation error for SYNCING status".to_string(),
3938 ));
3939 }
3940 Ok(Self::Syncing)
3941 }
3942 3 => {
3943 if !validation_error.is_empty() {
3944 return Err(ssz::DecodeError::BytesInvalid(
3945 "unexpected validation error for ACCEPTED status".to_string(),
3946 ));
3947 }
3948 Ok(Self::Accepted)
3949 }
3950 _ => Err(ssz::DecodeError::BytesInvalid("unknown payload status code".to_string())),
3951 }
3952 }
3953}
3954
3955impl core::fmt::Display for PayloadStatusEnum {
3956 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3957 match self {
3958 Self::Invalid { validation_error } => {
3959 f.write_str(self.as_str())?;
3960 f.write_str(": ")?;
3961 f.write_str(validation_error.as_str())
3962 }
3963 _ => f.write_str(self.as_str()),
3964 }
3965 }
3966}
3967
3968#[derive(Clone, Debug, PartialEq, Eq)]
3974#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
3975#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
3976#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
3977pub struct PayloadStatusV2 {
3978 #[cfg_attr(feature = "serde", serde(flatten))]
3980 pub payload_inner: PayloadStatus,
3981 #[cfg_attr(feature = "serde", serde(default))]
3983 pub inclusion_list_satisfied: Option<bool>,
3984}
3985
3986impl PayloadStatusV2 {
3987 pub const fn new(
3989 payload_status: PayloadStatus,
3990 inclusion_list_satisfied: Option<bool>,
3991 ) -> Self {
3992 Self { payload_inner: payload_status, inclusion_list_satisfied }
3993 }
3994
3995 pub const fn with_inclusion_list_satisfied(mut self, satisfied: bool) -> Self {
3997 self.inclusion_list_satisfied = Some(satisfied);
3998 self
3999 }
4000
4001 pub const fn is_syncing(&self) -> bool {
4003 self.payload_inner.is_syncing()
4004 }
4005
4006 pub const fn is_valid(&self) -> bool {
4008 self.payload_inner.is_valid()
4009 }
4010
4011 pub const fn is_invalid(&self) -> bool {
4013 self.payload_inner.is_invalid()
4014 }
4015}
4016
4017impl From<PayloadStatus> for PayloadStatusV2 {
4018 fn from(payload_status: PayloadStatus) -> Self {
4019 Self::new(payload_status, None)
4020 }
4021}
4022
4023impl From<PayloadStatusV2> for PayloadStatus {
4025 fn from(payload_status: PayloadStatusV2) -> Self {
4026 payload_status.payload_inner
4027 }
4028}
4029
4030#[cfg(feature = "serde")]
4031impl serde::Serialize for PayloadStatusV2 {
4032 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
4033 where
4034 S: serde::Serializer,
4035 {
4036 use serde::ser::SerializeMap;
4037
4038 let mut map = serializer.serialize_map(Some(4))?;
4039 map.serialize_entry("status", self.payload_inner.status.as_str())?;
4040 map.serialize_entry("latestValidHash", &self.payload_inner.latest_valid_hash)?;
4041 map.serialize_entry("validationError", &self.payload_inner.status.validation_error())?;
4042 map.serialize_entry("inclusionListSatisfied", &self.inclusion_list_satisfied)?;
4043 map.end()
4044 }
4045}
4046
4047#[derive(Debug, Clone)]
4050#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
4051#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
4052pub struct ExecutionData {
4053 pub payload: ExecutionPayload,
4055 pub sidecar: ExecutionPayloadSidecar,
4057}
4058
4059impl ExecutionData {
4060 pub const fn new(payload: ExecutionPayload, sidecar: ExecutionPayloadSidecar) -> Self {
4062 Self { payload, sidecar }
4063 }
4064
4065 pub fn from_block_unchecked<T, H>(block_hash: B256, block: &Block<T, H>) -> Self
4078 where
4079 T: Encodable2718 + Transaction,
4080 H: BlockHeader,
4081 {
4082 let (payload, sidecar) = ExecutionPayload::from_block_unchecked(block_hash, block);
4083 Self::new(payload, sidecar)
4084 }
4085
4086 pub const fn parent_hash(&self) -> B256 {
4088 self.payload.parent_hash()
4089 }
4090
4091 pub const fn block_hash(&self) -> B256 {
4093 self.payload.block_hash()
4094 }
4095
4096 pub const fn block_number(&self) -> u64 {
4098 self.payload.block_number()
4099 }
4100
4101 pub fn parent_beacon_block_root(&self) -> Option<B256> {
4103 self.sidecar.parent_beacon_block_root()
4104 }
4105
4106 pub const fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
4108 self.payload.withdrawals()
4109 }
4110
4111 pub const fn transaction_count(&self) -> usize {
4113 self.payload.transactions().len()
4114 }
4115
4116 pub fn try_into_block<T: Decodable2718>(
4127 self,
4128 ) -> Result<alloy_consensus::Block<T>, PayloadError> {
4129 self.try_into_block_with(|tx| {
4130 T::decode_2718_exact(tx.as_ref())
4131 .map_err(alloy_rlp::Error::from)
4132 .map_err(PayloadError::from)
4133 })
4134 }
4135
4136 pub fn try_into_block_with<T, F, E>(
4147 self,
4148 f: F,
4149 ) -> Result<alloy_consensus::Block<T>, PayloadError>
4150 where
4151 F: FnMut(Bytes) -> Result<T, E>,
4152 E: Into<PayloadError>,
4153 {
4154 self.payload.try_into_block_with_sidecar_with(&self.sidecar, f)
4155 }
4156
4157 pub fn into_block_raw(self) -> Result<Block<Bytes>, PayloadError> {
4162 let mut base_block = self.payload.into_block_raw()?;
4163 base_block.header.parent_beacon_block_root = self.sidecar.parent_beacon_block_root();
4164 base_block.header.requests_hash = self.sidecar.requests_hash();
4165 Ok(base_block)
4166 }
4167}
4168
4169impl<T, H> From<Sealed<Block<T, H>>> for ExecutionData
4170where
4171 T: Encodable2718 + Transaction,
4172 H: BlockHeader,
4173{
4174 fn from(sealed: Sealed<Block<T, H>>) -> Self {
4175 let (block, block_hash) = sealed.into_parts();
4176 Self::from_block_unchecked(block_hash, &block)
4177 }
4178}
4179
4180impl<T, H> From<Sealed<&Block<T, H>>> for ExecutionData
4181where
4182 T: Encodable2718 + Transaction,
4183 H: BlockHeader,
4184{
4185 fn from(sealed: Sealed<&Block<T, H>>) -> Self {
4186 let (block, block_hash) = sealed.into_parts();
4187 Self::from_block_unchecked(block_hash, block)
4188 }
4189}
4190
4191impl<T, H> From<(Sealed<Block<T, H>>, PayloadExtras)> for ExecutionData
4192where
4193 T: Encodable2718 + Transaction,
4194 H: BlockHeader,
4195{
4196 fn from((sealed, extras): (Sealed<Block<T, H>>, PayloadExtras)) -> Self {
4197 let (block, block_hash) = sealed.into_parts();
4198 let (payload, sidecar) =
4199 ExecutionPayload::from_block_unchecked_with_extras(block_hash, &block, extras);
4200 Self::new(payload, sidecar)
4201 }
4202}
4203
4204impl<T, H> From<(Sealed<&Block<T, H>>, PayloadExtras)> for ExecutionData
4205where
4206 T: Encodable2718 + Transaction,
4207 H: BlockHeader,
4208{
4209 fn from((sealed, extras): (Sealed<&Block<T, H>>, PayloadExtras)) -> Self {
4210 let (block, block_hash) = sealed.into_parts();
4211 let (payload, sidecar) =
4212 ExecutionPayload::from_block_unchecked_with_extras(block_hash, block, extras);
4213 Self::new(payload, sidecar)
4214 }
4215}
4216
4217#[cfg(test)]
4218mod tests {
4219 use super::*;
4220 use crate::{CancunPayloadFields, PayloadValidationError};
4221 use alloc::vec;
4222 use alloy_consensus::TxEnvelope;
4223 use alloy_primitives::{b256, hex};
4224 use similar_asserts::assert_eq;
4225
4226 #[test]
4227 #[cfg(feature = "kzg")]
4228 fn convert_empty_bundle() {
4229 let bundle = BlobsBundleV1::default();
4230 let _sidecar = bundle.try_into_sidecar().unwrap();
4231 }
4232
4233 #[test]
4234 #[cfg(feature = "serde")]
4235 fn serde_blobsbundlev1_empty() {
4236 let blobs_bundle_v1 = BlobsBundleV1::empty();
4237
4238 let serialized = serde_json::to_string(&blobs_bundle_v1).unwrap();
4239 let deserialized: BlobsBundleV1 = serde_json::from_str(&serialized).unwrap();
4240 assert_eq!(deserialized, blobs_bundle_v1);
4241 }
4242
4243 #[test]
4244 #[cfg(feature = "serde")]
4245 #[cfg(not(debug_assertions))]
4246 fn serde_blobsbundlev1_not_empty_pass() {
4247 let blobs_bundle_v1 = BlobsBundleV1 {
4248 proofs: vec![Bytes48::default()],
4249 commitments: vec![Bytes48::default()],
4250 blobs: vec![Blob::default()],
4251 };
4252
4253 let serialized = serde_json::to_string(&blobs_bundle_v1).unwrap();
4254 let deserialized: BlobsBundleV1 = serde_json::from_str(&serialized).unwrap();
4255 assert_eq!(deserialized, blobs_bundle_v1);
4256 }
4257
4258 #[test]
4259 #[cfg(feature = "serde")]
4260 #[cfg(not(debug_assertions))]
4261 fn serde_blobsbundlev1_not_empty_fail() {
4262 let blobs_bundle_v1 = BlobsBundleV1 {
4263 proofs: vec![Bytes48::default(), Bytes48::default()],
4264 commitments: vec![Bytes48::default()],
4265 blobs: vec![Blob::default()],
4266 };
4267
4268 let serialized = serde_json::to_string(&blobs_bundle_v1).unwrap();
4269 let deserialized: Result<BlobsBundleV1, serde_json::Error> =
4270 serde_json::from_str(&serialized);
4271 assert!(deserialized.is_err(), "invalid length 2, expected commitments.len()");
4272 }
4273
4274 #[test]
4275 #[cfg(feature = "serde")]
4276 #[cfg(not(debug_assertions))]
4277 fn serde_blobsbundlev2_not_empty_pass() {
4278 let commitments = vec![Bytes48::default()];
4279
4280 let blobs_bundle_v2 = BlobsBundleV2 {
4281 proofs: vec![Bytes48::default(); commitments.len() * CELLS_PER_EXT_BLOB],
4282 commitments,
4283 blobs: vec![Blob::default()],
4284 };
4285
4286 let serialized = serde_json::to_string(&blobs_bundle_v2).unwrap();
4287 let deserialized: BlobsBundleV2 = serde_json::from_str(&serialized).unwrap();
4288 assert_eq!(deserialized, blobs_bundle_v2);
4289 }
4290
4291 #[test]
4292 #[cfg(feature = "serde")]
4293 #[cfg(not(debug_assertions))]
4294 fn serde_blobsbundlev2_not_empty_fail() {
4295 let blobs_bundle_v2 = BlobsBundleV2 {
4296 proofs: vec![Bytes48::default()],
4297 commitments: vec![Bytes48::default()],
4298 blobs: vec![],
4299 };
4300
4301 let serialized = serde_json::to_string(&blobs_bundle_v2).unwrap();
4302 let deserialized: Result<BlobsBundleV2, serde_json::Error> =
4303 serde_json::from_str(&serialized);
4304 assert!(deserialized.is_err());
4305 }
4306
4307 #[test]
4308 #[cfg(feature = "ssz")]
4309 #[cfg(not(debug_assertions))]
4310 fn ssz_blobsbundlev2_roundtrip() {
4311 let commitments = vec![Bytes48::default(), Bytes48::default()];
4312 let num_blobs = commitments.len();
4313
4314 let blobs_bundle_v2 = BlobsBundleV2 {
4315 commitments,
4316 proofs: vec![Bytes48::default(); num_blobs * CELLS_PER_EXT_BLOB],
4317 blobs: vec![Blob::default(); num_blobs],
4318 };
4319
4320 let encoded = ssz::Encode::as_ssz_bytes(&blobs_bundle_v2);
4321 let decoded: BlobsBundleV2 = ssz::Decode::from_ssz_bytes(&encoded).unwrap();
4322
4323 assert_eq!(decoded, blobs_bundle_v2);
4324 }
4325
4326 #[test]
4327 #[cfg(feature = "ssz")]
4328 #[cfg(not(debug_assertions))]
4329 fn ssz_blobsbundlev2_invalid_proofs_length() {
4330 let commitments = vec![Bytes48::default()];
4331
4332 let blobs_bundle_v2 = BlobsBundleV2 {
4333 commitments,
4334 proofs: vec![Bytes48::default(); 2],
4335 blobs: vec![Blob::default()],
4336 };
4337
4338 let encoded = ssz::Encode::as_ssz_bytes(&blobs_bundle_v2);
4339
4340 let result: Result<BlobsBundleV2, _> = ssz::Decode::from_ssz_bytes(&encoded);
4342 assert!(result.is_err());
4343 }
4344
4345 #[test]
4346 #[cfg(feature = "ssz")]
4347 #[cfg(not(debug_assertions))]
4348 fn ssz_blobsbundlev2_mismatched_commitments_blobs() {
4349 let blobs_bundle_v2 = BlobsBundleV2 {
4350 commitments: vec![Bytes48::default(), Bytes48::default()],
4351 proofs: vec![Bytes48::default(); CELLS_PER_EXT_BLOB],
4352 blobs: vec![Blob::default()],
4353 };
4354
4355 let encoded = ssz::Encode::as_ssz_bytes(&blobs_bundle_v2);
4356
4357 let result: Result<BlobsBundleV2, _> = ssz::Decode::from_ssz_bytes(&encoded);
4359 assert!(result.is_err());
4360 }
4361
4362 #[test]
4363 #[cfg(feature = "ssz")]
4364 fn ssz_blobsbundlev2_empty() {
4365 let blobs_bundle_v2 = BlobsBundleV2 { commitments: vec![], proofs: vec![], blobs: vec![] };
4366
4367 let encoded = ssz::Encode::as_ssz_bytes(&blobs_bundle_v2);
4368
4369 let decoded: BlobsBundleV2 = ssz::Decode::from_ssz_bytes(&encoded).unwrap();
4371 assert_eq!(decoded, blobs_bundle_v2);
4372 }
4373
4374 #[cfg(feature = "ssz")]
4375 fn ssz_payload_v1() -> ExecutionPayloadV1 {
4376 ExecutionPayloadV1 {
4377 parent_hash: B256::with_last_byte(1),
4378 fee_recipient: Address::with_last_byte(2),
4379 state_root: B256::with_last_byte(3),
4380 receipts_root: B256::with_last_byte(4),
4381 logs_bloom: Bloom::default(),
4382 prev_randao: B256::with_last_byte(5),
4383 block_number: 6,
4384 gas_limit: 7,
4385 gas_used: 8,
4386 timestamp: 9,
4387 extra_data: Bytes::from(vec![10, 11]),
4388 base_fee_per_gas: U256::from(12),
4389 block_hash: B256::with_last_byte(13),
4390 transactions: vec![Bytes::from(vec![14, 15])],
4391 }
4392 }
4393
4394 #[cfg(feature = "ssz")]
4395 fn ssz_payload_v2() -> ExecutionPayloadV2 {
4396 ExecutionPayloadV2 {
4397 payload_inner: ssz_payload_v1(),
4398 withdrawals: vec![Withdrawal {
4399 index: 1,
4400 validator_index: 2,
4401 address: Address::with_last_byte(3),
4402 amount: 4,
4403 }],
4404 }
4405 }
4406
4407 #[cfg(feature = "ssz")]
4408 fn ssz_payload_v3() -> ExecutionPayloadV3 {
4409 ExecutionPayloadV3 {
4410 payload_inner: ssz_payload_v2(),
4411 blob_gas_used: 16,
4412 excess_blob_gas: 17,
4413 }
4414 }
4415
4416 #[cfg(feature = "ssz")]
4417 fn ssz_payload_v4() -> ExecutionPayloadV4 {
4418 ExecutionPayloadV4 {
4419 payload_inner: ssz_payload_v3(),
4420 block_access_list: Bytes::from(vec![18, 19]),
4421 slot_number: 20,
4422 }
4423 }
4424
4425 #[test]
4426 #[cfg(feature = "ssz")]
4427 fn ssz_execution_payload_envelope_v1_response_roundtrip() {
4428 use ssz::{Decode, Encode};
4429
4430 let payload = ssz_payload_v1();
4431 let decoded = ExecutionPayloadV1::from_ssz_bytes(&payload.as_ssz_bytes()).unwrap();
4432
4433 assert_eq!(decoded, payload);
4434 }
4435
4436 #[test]
4437 #[cfg(feature = "ssz")]
4438 fn ssz_execution_payload_envelope_v2_roundtrip() {
4439 use ssz::{Decode, Encode};
4440
4441 let envelope = ExecutionPayloadEnvelopeV2 {
4442 execution_payload: ExecutionPayloadFieldV2::V2(ssz_payload_v2()),
4443 block_value: U256::from(21),
4444 };
4445
4446 let decoded = ExecutionPayloadEnvelopeV2::from_ssz_bytes(&envelope.as_ssz_bytes()).unwrap();
4447 assert_eq!(decoded, envelope);
4448
4449 let envelope = ExecutionPayloadEnvelopeV2 {
4450 execution_payload: ExecutionPayloadFieldV2::V1(ssz_payload_v1()),
4451 block_value: U256::from(22),
4452 };
4453
4454 let decoded = ExecutionPayloadEnvelopeV2::from_ssz_bytes(&envelope.as_ssz_bytes()).unwrap();
4455 assert_eq!(decoded, envelope);
4456 }
4457
4458 #[test]
4459 #[cfg(feature = "ssz")]
4460 fn ssz_execution_payload_envelope_v3_roundtrip() {
4461 use ssz::{Decode, Encode};
4462
4463 let envelope = ExecutionPayloadEnvelopeV3 {
4464 execution_payload: ssz_payload_v3(),
4465 block_value: U256::from(23),
4466 blobs_bundle: BlobsBundleV1::empty(),
4467 should_override_builder: true,
4468 };
4469
4470 let decoded = ExecutionPayloadEnvelopeV3::from_ssz_bytes(&envelope.as_ssz_bytes()).unwrap();
4471 assert_eq!(decoded, envelope);
4472 }
4473
4474 #[test]
4475 #[cfg(feature = "ssz")]
4476 fn ssz_execution_payload_envelope_v4_roundtrip() {
4477 use ssz::{Decode, Encode};
4478
4479 let envelope = ExecutionPayloadEnvelopeV4 {
4480 envelope_inner: ExecutionPayloadEnvelopeV3 {
4481 execution_payload: ssz_payload_v3(),
4482 block_value: U256::from(24),
4483 blobs_bundle: BlobsBundleV1::empty(),
4484 should_override_builder: false,
4485 },
4486 execution_requests: Requests::from_requests([Bytes::from(vec![1, 2, 3])]),
4487 };
4488
4489 let decoded = ExecutionPayloadEnvelopeV4::from_ssz_bytes(&envelope.as_ssz_bytes()).unwrap();
4490 assert_eq!(decoded, envelope);
4491 }
4492
4493 #[test]
4494 #[cfg(feature = "ssz")]
4495 fn ssz_execution_payload_envelope_v5_roundtrip() {
4496 use ssz::{Decode, Encode};
4497
4498 let envelope = ExecutionPayloadEnvelopeV5 {
4499 execution_payload: ssz_payload_v3(),
4500 block_value: U256::from(25),
4501 blobs_bundle: BlobsBundleV2::empty(),
4502 should_override_builder: true,
4503 execution_requests: Requests::from_requests([Bytes::from(vec![4, 5, 6])]),
4504 };
4505
4506 let decoded = ExecutionPayloadEnvelopeV5::from_ssz_bytes(&envelope.as_ssz_bytes()).unwrap();
4507 assert_eq!(decoded, envelope);
4508 }
4509
4510 #[test]
4511 #[cfg(feature = "ssz")]
4512 fn ssz_execution_payload_envelope_v6_roundtrip() {
4513 use ssz::{Decode, Encode};
4514
4515 let envelope = ExecutionPayloadEnvelopeV6 {
4516 execution_payload: ssz_payload_v4(),
4517 block_value: U256::from(26),
4518 blobs_bundle: BlobsBundleV2::empty(),
4519 should_override_builder: false,
4520 execution_requests: Requests::from_requests([Bytes::from(vec![7, 8, 9])]),
4521 };
4522
4523 let decoded = ExecutionPayloadEnvelopeV6::from_ssz_bytes(&envelope.as_ssz_bytes()).unwrap();
4524 assert_eq!(decoded, envelope);
4525 }
4526
4527 #[test]
4528 #[cfg(feature = "serde")]
4529 fn serde_payload_status() {
4530 let s = r#"{"status":"SYNCING","latestValidHash":null,"validationError":null}"#;
4531 let status: PayloadStatus = serde_json::from_str(s).unwrap();
4532 assert_eq!(status.status, PayloadStatusEnum::Syncing);
4533 assert!(status.latest_valid_hash.is_none());
4534 assert!(status.status.validation_error().is_none());
4535 assert_eq!(serde_json::to_string(&status).unwrap(), s);
4536
4537 let full = s;
4538 let s = r#"{"status":"SYNCING","latestValidHash":null}"#;
4539 let status: PayloadStatus = serde_json::from_str(s).unwrap();
4540 assert_eq!(status.status, PayloadStatusEnum::Syncing);
4541 assert!(status.latest_valid_hash.is_none());
4542 assert!(status.status.validation_error().is_none());
4543 assert_eq!(serde_json::to_string(&status).unwrap(), full);
4544 }
4545
4546 #[test]
4547 #[cfg(feature = "serde")]
4548 fn serde_payload_status_v2() {
4549 let json = r#"{"status":"VALID","latestValidHash":null,"validationError":null,"inclusionListSatisfied":true}"#;
4550 let status: PayloadStatusV2 = serde_json::from_str(json).unwrap();
4551 assert!(status.is_valid());
4552 assert!(status.payload_inner.latest_valid_hash.is_none());
4553 assert_eq!(status.inclusion_list_satisfied, Some(true));
4554 assert_eq!(serde_json::to_string(&status).unwrap(), json);
4555
4556 let json = r#"{"status":"SYNCING","latestValidHash":null,"validationError":null,"inclusionListSatisfied":null}"#;
4557 let status: PayloadStatusV2 = serde_json::from_str(json).unwrap();
4558 assert!(status.is_syncing());
4559 assert_eq!(status.inclusion_list_satisfied, None);
4560 assert_eq!(serde_json::to_string(&status).unwrap(), json);
4561 }
4562
4563 #[test]
4564 fn payload_status_v2_conversions() {
4565 let v1 = PayloadStatus::from_status(PayloadStatusEnum::Valid)
4566 .with_latest_valid_hash(B256::with_last_byte(1));
4567
4568 let v2: PayloadStatusV2 = v1.clone().into();
4569 assert_eq!(v2.payload_inner, v1);
4570 assert_eq!(v2.inclusion_list_satisfied, None);
4571
4572 let downgraded: PayloadStatus = v2.with_inclusion_list_satisfied(true).into();
4573 assert_eq!(downgraded, v1);
4574 }
4575
4576 #[test]
4577 fn payload_attributes_builder_setters() {
4578 let withdrawal = Withdrawal {
4579 index: 1,
4580 validator_index: 2,
4581 address: Address::with_last_byte(3),
4582 amount: 4,
4583 };
4584 let parent_beacon_block_root = B256::with_last_byte(5);
4585
4586 let attributes = PayloadAttributes::default()
4587 .with_timestamp(10)
4588 .with_withdrawals(vec![withdrawal])
4589 .with_parent_beacon_block_root(parent_beacon_block_root)
4590 .with_slot_number(6);
4591
4592 assert_eq!(attributes.timestamp, 10);
4593 assert_eq!(attributes.withdrawals, Some(vec![withdrawal]));
4594 assert_eq!(attributes.parent_beacon_block_root, Some(parent_beacon_block_root));
4595 assert_eq!(attributes.slot_number, Some(6));
4596 }
4597
4598 #[test]
4599 #[cfg(feature = "ssz")]
4600 fn ssz_payload_attributes_roundtrip_all_versions() {
4601 use ssz::{Decode, Encode};
4602
4603 let withdrawal = Withdrawal {
4604 index: 1,
4605 validator_index: 2,
4606 address: Address::with_last_byte(3),
4607 amount: 4,
4608 };
4609
4610 let v1 = PayloadAttributes {
4611 timestamp: 10,
4612 prev_randao: B256::with_last_byte(11),
4613 suggested_fee_recipient: Address::with_last_byte(12),
4614 withdrawals: None,
4615 parent_beacon_block_root: None,
4616 slot_number: None,
4617 target_gas_limit: None,
4618 };
4619 let decoded_v1 = PayloadAttributes::from_ssz_bytes(&v1.as_ssz_bytes()).unwrap();
4620 assert_eq!(decoded_v1, v1);
4621
4622 let v2 = PayloadAttributes {
4623 timestamp: 20,
4624 prev_randao: B256::with_last_byte(21),
4625 suggested_fee_recipient: Address::with_last_byte(22),
4626 withdrawals: Some(vec![withdrawal]),
4627 parent_beacon_block_root: None,
4628 slot_number: None,
4629 target_gas_limit: None,
4630 };
4631 let decoded_v2 = PayloadAttributes::from_ssz_bytes(&v2.as_ssz_bytes()).unwrap();
4632 assert_eq!(decoded_v2, v2);
4633
4634 let v3 = PayloadAttributes {
4635 timestamp: 30,
4636 prev_randao: B256::with_last_byte(31),
4637 suggested_fee_recipient: Address::with_last_byte(32),
4638 withdrawals: Some(vec![withdrawal]),
4639 parent_beacon_block_root: Some(B256::with_last_byte(33)),
4640 slot_number: None,
4641 target_gas_limit: None,
4642 };
4643 let decoded_v3 = PayloadAttributes::from_ssz_bytes(&v3.as_ssz_bytes()).unwrap();
4644 assert_eq!(decoded_v3, v3);
4645
4646 let v4 = PayloadAttributes {
4647 timestamp: 40,
4648 prev_randao: B256::with_last_byte(41),
4649 suggested_fee_recipient: Address::with_last_byte(42),
4650 withdrawals: Some(vec![withdrawal]),
4651 parent_beacon_block_root: Some(B256::with_last_byte(43)),
4652 slot_number: Some(44),
4653 target_gas_limit: Some(45),
4654 };
4655 let decoded_v4 = PayloadAttributes::from_ssz_bytes(&v4.as_ssz_bytes()).unwrap();
4656 assert_eq!(decoded_v4, v4);
4657 }
4658
4659 #[test]
4660 #[cfg(feature = "ssz")]
4661 fn ssz_payload_attributes_match_spec_container_offsets() {
4662 use ssz::Encode;
4663
4664 let withdrawal = Withdrawal {
4665 index: 1,
4666 validator_index: 2,
4667 address: Address::with_last_byte(3),
4668 amount: 4,
4669 };
4670
4671 let v1 = PayloadAttributes {
4672 timestamp: 10,
4673 prev_randao: B256::with_last_byte(11),
4674 suggested_fee_recipient: Address::with_last_byte(12),
4675 withdrawals: None,
4676 parent_beacon_block_root: None,
4677 slot_number: None,
4678 target_gas_limit: None,
4679 };
4680 assert_eq!(v1.as_ssz_bytes().len(), 60);
4681
4682 let v2 = PayloadAttributes {
4683 timestamp: 20,
4684 prev_randao: B256::with_last_byte(21),
4685 suggested_fee_recipient: Address::with_last_byte(22),
4686 withdrawals: Some(vec![withdrawal]),
4687 parent_beacon_block_root: None,
4688 slot_number: None,
4689 target_gas_limit: None,
4690 };
4691 let bytes = v2.as_ssz_bytes();
4692 assert_eq!(u32::from_le_bytes(bytes[60..64].try_into().unwrap()), 64);
4693
4694 let v3 = PayloadAttributes {
4695 timestamp: 30,
4696 prev_randao: B256::with_last_byte(31),
4697 suggested_fee_recipient: Address::with_last_byte(32),
4698 withdrawals: Some(vec![withdrawal]),
4699 parent_beacon_block_root: Some(B256::with_last_byte(33)),
4700 slot_number: None,
4701 target_gas_limit: None,
4702 };
4703 let bytes = v3.as_ssz_bytes();
4704 assert_eq!(u32::from_le_bytes(bytes[60..64].try_into().unwrap()), 96);
4705
4706 let v4 = PayloadAttributes {
4707 timestamp: 40,
4708 prev_randao: B256::with_last_byte(41),
4709 suggested_fee_recipient: Address::with_last_byte(42),
4710 withdrawals: Some(vec![withdrawal]),
4711 parent_beacon_block_root: Some(B256::with_last_byte(43)),
4712 slot_number: Some(44),
4713 target_gas_limit: Some(45),
4714 };
4715 let bytes = v4.as_ssz_bytes();
4716 assert_eq!(u32::from_le_bytes(bytes[60..64].try_into().unwrap()), 112);
4717 }
4718
4719 #[test]
4720 #[cfg(feature = "ssz")]
4721 fn ssz_payload_status_roundtrip() {
4722 use ssz::{Decode, Encode};
4723
4724 let statuses = [
4725 PayloadStatus {
4726 status: PayloadStatusEnum::Valid,
4727 latest_valid_hash: Some(B256::with_last_byte(1)),
4728 },
4729 PayloadStatus {
4730 status: PayloadStatusEnum::Invalid { validation_error: "bad payload".to_string() },
4731 latest_valid_hash: Some(B256::with_last_byte(2)),
4732 },
4733 PayloadStatus { status: PayloadStatusEnum::Syncing, latest_valid_hash: None },
4734 PayloadStatus { status: PayloadStatusEnum::Accepted, latest_valid_hash: None },
4735 ];
4736
4737 for status in statuses {
4738 let decoded = PayloadStatus::from_ssz_bytes(&status.as_ssz_bytes()).unwrap();
4739 assert_eq!(decoded, status);
4740 }
4741 }
4742
4743 #[test]
4744 #[cfg(feature = "ssz")]
4745 fn ssz_payload_status_matches_eip8178_container() {
4746 use ssz::{Decode, Encode};
4747
4748 let status = PayloadStatus {
4749 status: PayloadStatusEnum::Invalid { validation_error: "bad payload".to_string() },
4750 latest_valid_hash: None,
4751 };
4752 let spec = (1u8, B256::ZERO, b"bad payload".to_vec());
4753
4754 assert_eq!(status.as_ssz_bytes(), spec.as_ssz_bytes());
4755 assert_eq!(PayloadStatus::from_ssz_bytes(&spec.as_ssz_bytes()).unwrap(), status);
4756 }
4757
4758 #[test]
4759 #[cfg(feature = "ssz")]
4760 fn ssz_payload_id_roundtrip() {
4761 use ssz::{Decode, Encode};
4762
4763 let payload_id = PayloadId(B64::with_last_byte(42));
4764 let decoded = PayloadId::from_ssz_bytes(&payload_id.as_ssz_bytes()).unwrap();
4765 assert_eq!(decoded, payload_id);
4766 }
4767
4768 #[test]
4769 fn payload_id_from_str() {
4770 let expected = PayloadId(B64::with_last_byte(42));
4771
4772 assert_eq!("0x000000000000002a".parse::<PayloadId>().unwrap(), expected);
4773 assert_eq!("000000000000002a".parse::<PayloadId>().unwrap(), expected);
4774 }
4775
4776 #[test]
4777 fn payload_id_from_str_rejects_invalid_hex() {
4778 assert!("0x2a".parse::<PayloadId>().is_err());
4779 assert!("0x00000000000000zz".parse::<PayloadId>().is_err());
4780 }
4781
4782 #[test]
4783 #[cfg(feature = "serde")]
4784 fn serde_payload_status_error_deserialize() {
4785 let s = r#"{"status":"INVALID","latestValidHash":null,"validationError":"Failed to decode block"}"#;
4786 let q = PayloadStatus {
4787 latest_valid_hash: None,
4788 status: PayloadStatusEnum::Invalid {
4789 validation_error: "Failed to decode block".to_string(),
4790 },
4791 };
4792 assert_eq!(q, serde_json::from_str(s).unwrap());
4793
4794 let s = r#"{"status":"INVALID","latestValidHash":null,"validationError":"links to previously rejected block"}"#;
4795 let q = PayloadStatus {
4796 latest_valid_hash: None,
4797 status: PayloadStatusEnum::Invalid {
4798 validation_error: PayloadValidationError::LinksToRejectedPayload.to_string(),
4799 },
4800 };
4801 assert_eq!(q, serde_json::from_str(s).unwrap());
4802
4803 let s = r#"{"status":"INVALID","latestValidHash":null,"validationError":"invalid block number"}"#;
4804 let q = PayloadStatus {
4805 latest_valid_hash: None,
4806 status: PayloadStatusEnum::Invalid {
4807 validation_error: PayloadValidationError::InvalidBlockNumber.to_string(),
4808 },
4809 };
4810 assert_eq!(q, serde_json::from_str(s).unwrap());
4811
4812 let s = r#"{"status":"INVALID","latestValidHash":null,"validationError":
4813 "invalid merkle root: (remote: 0x3f77fb29ce67436532fee970e1add8f5cc80e8878c79b967af53b1fd92a0cab7 local: 0x603b9628dabdaadb442a3bb3d7e0360efc110e1948472909230909f1690fed17)"}"#;
4814 let q = PayloadStatus {
4815 latest_valid_hash: None,
4816 status: PayloadStatusEnum::Invalid {
4817 validation_error: PayloadValidationError::InvalidStateRoot {
4818 remote: "0x3f77fb29ce67436532fee970e1add8f5cc80e8878c79b967af53b1fd92a0cab7"
4819 .parse()
4820 .unwrap(),
4821 local: "0x603b9628dabdaadb442a3bb3d7e0360efc110e1948472909230909f1690fed17"
4822 .parse()
4823 .unwrap(),
4824 }
4825 .to_string(),
4826 },
4827 };
4828 assert_eq!(q, serde_json::from_str(s).unwrap());
4829 }
4830
4831 #[test]
4832 #[cfg(feature = "serde")]
4833 fn serde_roundtrip_legacy_txs_payload_v1() {
4834 let s = r#"{"parentHash":"0x67ead97eb79b47a1638659942384143f36ed44275d4182799875ab5a87324055","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x4e3c608a9f2e129fccb91a1dae7472e78013b8e654bccc8d224ce3d63ae17006","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0x44bb4b98c59dbb726f96ffceb5ee028dcbe35b9bba4f9ffd56aeebf8d1e4db62","blockNumber":"0x1","gasLimit":"0x2fefd8","gasUsed":"0xa860","timestamp":"0x1235","extraData":"0x8b726574682f76302e312e30","baseFeePerGas":"0x342770c0","blockHash":"0x5655011482546f16b2312ef18e9fad03d6a52b1be95401aea884b222477f9e64","transactions":["0xf865808506fc23ac00830124f8940000000000000000000000000000000000000316018032a044b25a8b9b247d01586b3d59c71728ff49c9b84928d9e7fa3377ead3b5570b5da03ceac696601ff7ee6f5fe8864e2998db9babdf5eeba1a0cd5b4d44b3fcbd181b"]}"#;
4836 let payload: ExecutionPayloadV1 = serde_json::from_str(s).unwrap();
4837 assert_eq!(serde_json::to_string(&payload).unwrap(), s);
4838
4839 let any_payload: ExecutionPayload = serde_json::from_str(s).unwrap();
4840 assert_eq!(any_payload, payload.into());
4841 }
4842
4843 #[test]
4844 #[cfg(feature = "serde")]
4845 fn serde_roundtrip_legacy_txs_payload_v3() {
4846 let s = r#"{"parentHash":"0x67ead97eb79b47a1638659942384143f36ed44275d4182799875ab5a87324055","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x4e3c608a9f2e129fccb91a1dae7472e78013b8e654bccc8d224ce3d63ae17006","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0x44bb4b98c59dbb726f96ffceb5ee028dcbe35b9bba4f9ffd56aeebf8d1e4db62","blockNumber":"0x1","gasLimit":"0x2fefd8","gasUsed":"0xa860","timestamp":"0x1235","extraData":"0x8b726574682f76302e312e30","baseFeePerGas":"0x342770c0","blockHash":"0x5655011482546f16b2312ef18e9fad03d6a52b1be95401aea884b222477f9e64","transactions":["0xf865808506fc23ac00830124f8940000000000000000000000000000000000000316018032a044b25a8b9b247d01586b3d59c71728ff49c9b84928d9e7fa3377ead3b5570b5da03ceac696601ff7ee6f5fe8864e2998db9babdf5eeba1a0cd5b4d44b3fcbd181b"],"withdrawals":[],"blobGasUsed":"0xb10b","excessBlobGas":"0xb10b"}"#;
4848 let payload: ExecutionPayloadV3 = serde_json::from_str(s).unwrap();
4849 assert_eq!(serde_json::to_string(&payload).unwrap(), s);
4850
4851 let any_payload: ExecutionPayload = serde_json::from_str(s).unwrap();
4852 assert_eq!(any_payload, payload.into());
4853 }
4854
4855 #[test]
4856 #[cfg(feature = "serde")]
4857 fn serde_roundtrip_enveloped_txs_payload_v1() {
4858 let s = r#"{"parentHash":"0x67ead97eb79b47a1638659942384143f36ed44275d4182799875ab5a87324055","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x76a03cbcb7adce07fd284c61e4fa31e5e786175cefac54a29e46ec8efa28ea41","receiptsRoot":"0x4e3c608a9f2e129fccb91a1dae7472e78013b8e654bccc8d224ce3d63ae17006","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0x028111cb7d25918386a69656b3d17b2febe95fd0f11572c1a55c14f99fdfe3df","blockNumber":"0x1","gasLimit":"0x2fefd8","gasUsed":"0xa860","timestamp":"0x1235","extraData":"0x8b726574682f76302e312e30","baseFeePerGas":"0x342770c0","blockHash":"0xa6f40ed042e61e88e76125dede8fff8026751ea14454b68fb534cea99f2b2a77","transactions":["0xf865808506fc23ac00830124f8940000000000000000000000000000000000000316018032a044b25a8b9b247d01586b3d59c71728ff49c9b84928d9e7fa3377ead3b5570b5da03ceac696601ff7ee6f5fe8864e2998db9babdf5eeba1a0cd5b4d44b3fcbd181b"]}"#;
4860 let payload: ExecutionPayloadV1 = serde_json::from_str(s).unwrap();
4861 assert_eq!(serde_json::to_string(&payload).unwrap(), s);
4862
4863 let any_payload: ExecutionPayload = serde_json::from_str(s).unwrap();
4864 assert_eq!(any_payload, payload.into());
4865 }
4866
4867 #[test]
4868 #[cfg(feature = "serde")]
4869 fn serde_roundtrip_enveloped_txs_payload_v3() {
4870 let s = r#"{"parentHash":"0x67ead97eb79b47a1638659942384143f36ed44275d4182799875ab5a87324055","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x76a03cbcb7adce07fd284c61e4fa31e5e786175cefac54a29e46ec8efa28ea41","receiptsRoot":"0x4e3c608a9f2e129fccb91a1dae7472e78013b8e654bccc8d224ce3d63ae17006","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0x028111cb7d25918386a69656b3d17b2febe95fd0f11572c1a55c14f99fdfe3df","blockNumber":"0x1","gasLimit":"0x2fefd8","gasUsed":"0xa860","timestamp":"0x1235","extraData":"0x8b726574682f76302e312e30","baseFeePerGas":"0x342770c0","blockHash":"0xa6f40ed042e61e88e76125dede8fff8026751ea14454b68fb534cea99f2b2a77","transactions":["0xf865808506fc23ac00830124f8940000000000000000000000000000000000000316018032a044b25a8b9b247d01586b3d59c71728ff49c9b84928d9e7fa3377ead3b5570b5da03ceac696601ff7ee6f5fe8864e2998db9babdf5eeba1a0cd5b4d44b3fcbd181b"],"withdrawals":[],"blobGasUsed":"0xb10b","excessBlobGas":"0xb10b"}"#;
4872 let payload: ExecutionPayloadV3 = serde_json::from_str(s).unwrap();
4873 assert_eq!(serde_json::to_string(&payload).unwrap(), s);
4874
4875 let any_payload: ExecutionPayload = serde_json::from_str(s).unwrap();
4876 assert_eq!(any_payload, payload.into());
4877 }
4878
4879 #[test]
4880 #[cfg(feature = "serde")]
4881 fn serde_roundtrip_execution_payload_envelope_v3() {
4882 let response = r#"{"executionPayload":{"parentHash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x10f8a0830000e8edef6d00cc727ff833f064b1950afd591ae41357f97e543119","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe0d8b4521a7da1582a713244ffb6a86aa1726932087386e2dc7973f43fc6cb24","blockNumber":"0x1","gasLimit":"0x2ffbd2","gasUsed":"0x0","timestamp":"0x1235","extraData":"0xd883010d00846765746888676f312e32312e30856c696e7578","baseFeePerGas":"0x342770c0","blockHash":"0x44d0fa5f2f73a938ebb96a2a21679eb8dea3e7b7dd8fd9f35aa756dda8bf0a8a","transactions":[],"withdrawals":[],"blobGasUsed":"0x0","excessBlobGas":"0x0"},"blockValue":"0x0","blobsBundle":{"commitments":[],"proofs":[],"blobs":[]},"shouldOverrideBuilder":false}"#;
4884 let envelope: ExecutionPayloadEnvelopeV3 = serde_json::from_str(response).unwrap();
4885 assert_eq!(serde_json::to_string(&envelope).unwrap(), response);
4886 }
4887
4888 #[test]
4889 #[cfg(feature = "serde")]
4890 fn serde_roundtrip_execution_payload_field_v2() {
4891 let s = r#"{"parentHash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x10f8a0830000e8edef6d00cc727ff833f064b1950afd591ae41357f97e543119","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe0d8b4521a7da1582a713244ffb6a86aa1726932087386e2dc7973f43fc6cb24","blockNumber":"0x1","gasLimit":"0x2ffbd2","gasUsed":"0x0","timestamp":"0x1235","extraData":"0xd883010d00846765746888676f312e32312e30856c696e7578","baseFeePerGas":"0x342770c0","blockHash":"0x44d0fa5f2f73a938ebb96a2a21679eb8dea3e7b7dd8fd9f35aa756dda8bf0a8a","transactions":[],"withdrawals":[{"index":"0x0","validatorIndex":"0x1","address":"0x00000000000000000000000000000000000010f0","amount":"0x64"}]}"#;
4893 let field: ExecutionPayloadFieldV2 = serde_json::from_str(s).unwrap();
4894 let payload_v2: ExecutionPayloadV2 = serde_json::from_str(s).unwrap();
4895 assert_eq!(field, ExecutionPayloadFieldV2::V2(payload_v2));
4896 assert_eq!(serde_json::to_string(&field).unwrap(), s);
4897
4898 let s_empty = s.replace(
4900 r#"[{"index":"0x0","validatorIndex":"0x1","address":"0x00000000000000000000000000000000000010f0","amount":"0x64"}]"#,
4901 "[]",
4902 );
4903 let field: ExecutionPayloadFieldV2 = serde_json::from_str(&s_empty).unwrap();
4904 let payload_v2: ExecutionPayloadV2 = serde_json::from_str(&s_empty).unwrap();
4905 assert_eq!(field, ExecutionPayloadFieldV2::V2(payload_v2));
4906 assert_eq!(serde_json::to_string(&field).unwrap(), s_empty);
4907
4908 let s_v1 = s_empty.replace(r#","withdrawals":[]"#, "");
4910 let field: ExecutionPayloadFieldV2 = serde_json::from_str(&s_v1).unwrap();
4911 let payload_v1: ExecutionPayloadV1 = serde_json::from_str(&s_v1).unwrap();
4912 assert_eq!(field, ExecutionPayloadFieldV2::V1(payload_v1));
4913 assert_eq!(serde_json::to_string(&field).unwrap(), s_v1);
4914 }
4915
4916 #[test]
4917 #[cfg(feature = "serde")]
4918 fn serde_roundtrip_execution_payload_envelope_v2() {
4919 let response = r#"{"executionPayload":{"parentHash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x10f8a0830000e8edef6d00cc727ff833f064b1950afd591ae41357f97e543119","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe0d8b4521a7da1582a713244ffb6a86aa1726932087386e2dc7973f43fc6cb24","blockNumber":"0x1","gasLimit":"0x2ffbd2","gasUsed":"0x0","timestamp":"0x1235","extraData":"0xd883010d00846765746888676f312e32312e30856c696e7578","baseFeePerGas":"0x342770c0","blockHash":"0x44d0fa5f2f73a938ebb96a2a21679eb8dea3e7b7dd8fd9f35aa756dda8bf0a8a","transactions":[],"withdrawals":[{"index":"0x0","validatorIndex":"0x1","address":"0x00000000000000000000000000000000000010f0","amount":"0x64"}]},"blockValue":"0x123"}"#;
4921 let envelope: ExecutionPayloadEnvelopeV2 = serde_json::from_str(response).unwrap();
4922 assert!(matches!(envelope.execution_payload, ExecutionPayloadFieldV2::V2(_)));
4923 assert_eq!(serde_json::to_string(&envelope).unwrap(), response);
4924 }
4925
4926 #[test]
4927 #[cfg(feature = "serde")]
4928 fn serde_payload_input_enum_v3() {
4929 let response_v3 = r#"{"parentHash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x10f8a0830000e8edef6d00cc727ff833f064b1950afd591ae41357f97e543119","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe0d8b4521a7da1582a713244ffb6a86aa1726932087386e2dc7973f43fc6cb24","blockNumber":"0x1","gasLimit":"0x2ffbd2","gasUsed":"0x0","timestamp":"0x1235","extraData":"0xd883010d00846765746888676f312e32312e30856c696e7578","baseFeePerGas":"0x342770c0","blockHash":"0x44d0fa5f2f73a938ebb96a2a21679eb8dea3e7b7dd8fd9f35aa756dda8bf0a8a","transactions":[],"withdrawals":[],"blobGasUsed":"0x0","excessBlobGas":"0x0"}"#;
4930
4931 let payload: ExecutionPayload = serde_json::from_str(response_v3).unwrap();
4932 assert!(payload.as_v3().is_some());
4933 assert_eq!(serde_json::to_string(&payload).unwrap(), response_v3);
4934
4935 let payload_v3: ExecutionPayloadV3 = serde_json::from_str(response_v3).unwrap();
4936 assert_eq!(payload.as_v3().unwrap(), &payload_v3);
4937 }
4938
4939 #[test]
4940 #[cfg(feature = "serde")]
4941 fn serde_payload_input_enum_v2() {
4942 let response_v2 = r#"{"parentHash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x10f8a0830000e8edef6d00cc727ff833f064b1950afd591ae41357f97e543119","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe0d8b4521a7da1582a713244ffb6a86aa1726932087386e2dc7973f43fc6cb24","blockNumber":"0x1","gasLimit":"0x2ffbd2","gasUsed":"0x0","timestamp":"0x1235","extraData":"0xd883010d00846765746888676f312e32312e30856c696e7578","baseFeePerGas":"0x342770c0","blockHash":"0x44d0fa5f2f73a938ebb96a2a21679eb8dea3e7b7dd8fd9f35aa756dda8bf0a8a","transactions":[],"withdrawals":[]}"#;
4943
4944 let payload: ExecutionPayload = serde_json::from_str(response_v2).unwrap();
4945 assert!(payload.as_v3().is_none());
4946 assert!(payload.as_v2().is_some());
4947 assert_eq!(serde_json::to_string(&payload).unwrap(), response_v2);
4948
4949 let payload_v2: ExecutionPayloadV2 = serde_json::from_str(response_v2).unwrap();
4950 assert_eq!(payload.as_v2().unwrap(), &payload_v2);
4951 }
4952
4953 #[test]
4954 #[cfg(feature = "serde")]
4955 fn serde_payload_input_enum_faulty_v2() {
4956 let response_faulty = r#"{"parentHash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x10f8a0830000e8edef6d00cc727ff833f064b1950afd591ae41357f97e543119","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe0d8b4521a7da1582a713244ffb6a86aa1726932087386e2dc7973f43fc6cb24","blockNumber":"0x1","gasLimit":"0x2ffbd2","gasUsed":"0x0","timestamp":"0x1235","extraData":"0xd883010d00846765746888676f312e32312e30856c696e7578","baseFeePerGas":"0x342770c0","blockHash":"0x44d0fa5f2f73a938ebb96a2a21679eb8dea3e7b7dd8fd9f35aa756dda8bf0a8a","transactions":[],"withdrawals":[], "blobGasUsed": "0x0"}"#;
4958
4959 let payload: Result<ExecutionPayload, serde_json::Error> =
4960 serde_json::from_str(response_faulty);
4961 assert!(payload.is_err());
4962 }
4963
4964 #[test]
4965 #[cfg(feature = "serde")]
4966 fn serde_payload_input_enum_faulty_v1() {
4967 let response_faulty = r#"{"parentHash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x10f8a0830000e8edef6d00cc727ff833f064b1950afd591ae41357f97e543119","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe0d8b4521a7da1582a713244ffb6a86aa1726932087386e2dc7973f43fc6cb24","blockNumber":"0x1","gasLimit":"0x2ffbd2","gasUsed":"0x0","timestamp":"0x1235","extraData":"0xd883010d00846765746888676f312e32312e30856c696e7578","baseFeePerGas":"0x342770c0","blockHash":"0x44d0fa5f2f73a938ebb96a2a21679eb8dea3e7b7dd8fd9f35aa756dda8bf0a8a","transactions":[],"blobGasUsed": "0x0"}"#;
4969
4970 let payload: Result<ExecutionPayload, serde_json::Error> =
4971 serde_json::from_str(response_faulty);
4972 assert!(payload.is_err());
4973 }
4974
4975 #[test]
4976 #[cfg(feature = "serde")]
4977 fn serde_faulty_roundtrip_payload_input_v3() {
4978 let response_v3 = r#"{"parentHash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x10f8a0830000e8edef6d00cc727ff833f064b1950afd591ae41357f97e543119","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe0d8b4521a7da1582a713244ffb6a86aa1726932087386e2dc7973f43fc6cb24","blockNumber":"0x1","gasLimit":"0x2ffbd2","gasUsed":"0x0","timestamp":"0x1235","extraData":"0xd883010d00846765746888676f312e32312e30856c696e7578","baseFeePerGas":"0x342770c0","blockHash":"0x44d0fa5f2f73a938ebb96a2a21679eb8dea3e7b7dd8fd9f35aa756dda8bf0a8a","transactions":[],"withdrawals":[],"blobGasUsed":"0x0","excessBlobGas":"0x0"}"#;
4982
4983 let payload_v2: ExecutionPayloadV2 = serde_json::from_str(response_v3).unwrap();
4984 assert_ne!(response_v3, serde_json::to_string(&payload_v2).unwrap());
4985
4986 let payload_v1: ExecutionPayloadV1 = serde_json::from_str(response_v3).unwrap();
4987 assert_ne!(response_v3, serde_json::to_string(&payload_v1).unwrap());
4988 }
4989
4990 #[test]
4991 #[cfg(feature = "serde")]
4992 fn serde_faulty_roundtrip_payload_input_v2() {
4993 let response_v2 = r#"{"parentHash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x10f8a0830000e8edef6d00cc727ff833f064b1950afd591ae41357f97e543119","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe0d8b4521a7da1582a713244ffb6a86aa1726932087386e2dc7973f43fc6cb24","blockNumber":"0x1","gasLimit":"0x2ffbd2","gasUsed":"0x0","timestamp":"0x1235","extraData":"0xd883010d00846765746888676f312e32312e30856c696e7578","baseFeePerGas":"0x342770c0","blockHash":"0x44d0fa5f2f73a938ebb96a2a21679eb8dea3e7b7dd8fd9f35aa756dda8bf0a8a","transactions":[],"withdrawals":[]}"#;
4997
4998 let payload: ExecutionPayloadV1 = serde_json::from_str(response_v2).unwrap();
4999 assert_ne!(response_v2, serde_json::to_string(&payload).unwrap());
5000 }
5001
5002 #[test]
5003 #[cfg(feature = "serde")]
5004 fn serde_deserialize_execution_payload_input_v2() {
5005 let response = r#"
5006{
5007 "baseFeePerGas": "0x173b30b3",
5008 "blockHash": "0x99d486755fd046ad0bbb60457bac93d4856aa42fa00629cc7e4a28b65b5f8164",
5009 "blockNumber": "0xb",
5010 "extraData": "0xd883010d01846765746888676f312e32302e33856c696e7578",
5011 "feeRecipient": "0x0000000000000000000000000000000000000000",
5012 "gasLimit": "0x405829",
5013 "gasUsed": "0x3f0ca0",
5014 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
5015 "parentHash": "0xfe34aaa2b869c66a727783ee5ad3e3983b6ef22baf24a1e502add94e7bcac67a",
5016 "prevRandao": "0x74132c32fe3ab9a470a8352544514d21b6969e7749f97742b53c18a1b22b396c",
5017 "receiptsRoot": "0x6a5c41dc55a1bd3e74e7f6accc799efb08b00c36c15265058433fcea6323e95f",
5018 "stateRoot": "0xde3b357f5f099e4c33d0343c9e9d204d663d7bd9c65020a38e5d0b2a9ace78a2",
5019 "timestamp": "0x6507d6b4",
5020 "transactions": [
5021 "0xf86d0a8458b20efd825208946177843db3138ae69679a54b95cf345ed759450d8806f3e8d87878800080820a95a0f8bddb1dcc4558b532ff747760a6f547dd275afdbe7bdecc90680e71de105757a014f34ba38c180913c0543b0ac2eccfb77cc3f801a535008dc50e533fbe435f53",
5022 "0xf86d0b8458b20efd82520894687704db07e902e9a8b3754031d168d46e3d586e8806f3e8d87878800080820a95a0e3108f710902be662d5c978af16109961ffaf2ac4f88522407d40949a9574276a0205719ed21889b42ab5c1026d40b759a507c12d92db0d100fa69e1ac79137caa",
5023 "0xf86d0c8458b20efd8252089415e6a5a2e131dd5467fa1ff3acd104f45ee5940b8806f3e8d87878800080820a96a0af556ba9cda1d686239e08c24e169dece7afa7b85e0948eaa8d457c0561277fca029da03d3af0978322e54ac7e8e654da23934e0dd839804cb0430f8aaafd732dc",
5024 "0xf8521784565adcb7830186a0808080820a96a0ec782872a673a9fe4eff028a5bdb30d6b8b7711f58a187bf55d3aec9757cb18ea001796d373da76f2b0aeda72183cce0ad070a4f03aa3e6fee4c757a9444245206",
5025 "0xf8521284565adcb7830186a0808080820a95a08a0ea89028eff02596b385a10e0bd6ae098f3b281be2c95a9feb1685065d7384a06239d48a72e4be767bd12f317dd54202f5623a33e71e25a87cb25dd781aa2fc8",
5026 "0xf8521384565adcb7830186a0808080820a95a0784dbd311a82f822184a46f1677a428cbe3a2b88a798fb8ad1370cdbc06429e8a07a7f6a0efd428e3d822d1de9a050b8a883938b632185c254944dd3e40180eb79"
5027 ],
5028 "withdrawals": []
5029}
5030 "#;
5031 let payload: ExecutionPayloadInputV2 = serde_json::from_str(response).unwrap();
5032 assert_eq!(payload.withdrawals, Some(vec![]));
5033
5034 let response = r#"
5035{
5036 "baseFeePerGas": "0x173b30b3",
5037 "blockHash": "0x99d486755fd046ad0bbb60457bac93d4856aa42fa00629cc7e4a28b65b5f8164",
5038 "blockNumber": "0xb",
5039 "extraData": "0xd883010d01846765746888676f312e32302e33856c696e7578",
5040 "feeRecipient": "0x0000000000000000000000000000000000000000",
5041 "gasLimit": "0x405829",
5042 "gasUsed": "0x3f0ca0",
5043 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
5044 "parentHash": "0xfe34aaa2b869c66a727783ee5ad3e3983b6ef22baf24a1e502add94e7bcac67a",
5045 "prevRandao": "0x74132c32fe3ab9a470a8352544514d21b6969e7749f97742b53c18a1b22b396c",
5046 "receiptsRoot": "0x6a5c41dc55a1bd3e74e7f6accc799efb08b00c36c15265058433fcea6323e95f",
5047 "stateRoot": "0xde3b357f5f099e4c33d0343c9e9d204d663d7bd9c65020a38e5d0b2a9ace78a2",
5048 "timestamp": "0x6507d6b4",
5049 "transactions": [
5050 "0xf86d0a8458b20efd825208946177843db3138ae69679a54b95cf345ed759450d8806f3e8d87878800080820a95a0f8bddb1dcc4558b532ff747760a6f547dd275afdbe7bdecc90680e71de105757a014f34ba38c180913c0543b0ac2eccfb77cc3f801a535008dc50e533fbe435f53",
5051 "0xf86d0b8458b20efd82520894687704db07e902e9a8b3754031d168d46e3d586e8806f3e8d87878800080820a95a0e3108f710902be662d5c978af16109961ffaf2ac4f88522407d40949a9574276a0205719ed21889b42ab5c1026d40b759a507c12d92db0d100fa69e1ac79137caa",
5052 "0xf86d0c8458b20efd8252089415e6a5a2e131dd5467fa1ff3acd104f45ee5940b8806f3e8d87878800080820a96a0af556ba9cda1d686239e08c24e169dece7afa7b85e0948eaa8d457c0561277fca029da03d3af0978322e54ac7e8e654da23934e0dd839804cb0430f8aaafd732dc",
5053 "0xf8521784565adcb7830186a0808080820a96a0ec782872a673a9fe4eff028a5bdb30d6b8b7711f58a187bf55d3aec9757cb18ea001796d373da76f2b0aeda72183cce0ad070a4f03aa3e6fee4c757a9444245206",
5054 "0xf8521284565adcb7830186a0808080820a95a08a0ea89028eff02596b385a10e0bd6ae098f3b281be2c95a9feb1685065d7384a06239d48a72e4be767bd12f317dd54202f5623a33e71e25a87cb25dd781aa2fc8",
5055 "0xf8521384565adcb7830186a0808080820a95a0784dbd311a82f822184a46f1677a428cbe3a2b88a798fb8ad1370cdbc06429e8a07a7f6a0efd428e3d822d1de9a050b8a883938b632185c254944dd3e40180eb79"
5056 ]
5057}
5058 "#;
5059 let payload: ExecutionPayloadInputV2 = serde_json::from_str(response).unwrap();
5060 assert_eq!(payload.withdrawals, None);
5061 }
5062
5063 #[test]
5064 #[cfg(feature = "serde")]
5065 fn serde_deserialize_v2_input_with_blob_fields() {
5066 let input = r#"
5067{
5068 "parentHash": "0xaaa4c5b574f37e1537c78931d1bca24a4d17d4f29f1ee97e1cd48b704909de1f",
5069 "feeRecipient": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
5070 "stateRoot": "0x308ee9c5c6fab5e3d08763a3b5fe0be8ada891fa5010a49a3390e018dd436810",
5071 "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
5072 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
5073 "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000",
5074 "blockNumber": "0xf",
5075 "gasLimit": "0x16345785d8a0000",
5076 "gasUsed": "0x0",
5077 "timestamp": "0x3a97",
5078 "extraData": "0x",
5079 "baseFeePerGas": "0x7",
5080 "blockHash": "0x38bb6ba645c7e6bd970f9c7d492fafe1e04d85349054cb48d16c9d2c3e3cd0bf",
5081 "transactions": [],
5082 "withdrawals": [],
5083 "excessBlobGas": "0x0",
5084 "blobGasUsed": "0x0"
5085}
5086 "#;
5087
5088 let payload_res: Result<ExecutionPayloadInputV2, serde_json::Error> =
5090 serde_json::from_str(input);
5091 assert!(payload_res.is_err());
5092 }
5093
5094 #[test]
5096 #[cfg(feature = "serde")]
5097 fn deserialize_op_base_payload() {
5098 let payload = r#"{"parentHash":"0x24e8df372a61cdcdb1a163b52aaa1785e0c869d28c3b742ac09e826bbb524723","feeRecipient":"0x4200000000000000000000000000000000000011","stateRoot":"0x9a5db45897f1ff1e620a6c14b0a6f1b3bcdbed59f2adc516a34c9a9d6baafa71","receiptsRoot":"0x8af6f74835d47835deb5628ca941d00e0c9fd75585f26dabdcb280ec7122e6af","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xf37b24eeff594848072a05f74c8600001706c83e489a9132e55bf43a236e42ec","blockNumber":"0xe3d5d8","gasLimit":"0x17d7840","gasUsed":"0xb705","timestamp":"0x65a118c0","extraData":"0x","baseFeePerGas":"0x7a0ff32","blockHash":"0xf5c147b2d60a519b72434f0a8e082e18599021294dd9085d7597b0ffa638f1c0","withdrawals":[],"transactions":["0x7ef90159a05ba0034ffdcb246703298224564720b66964a6a69d0d7e9ffd970c546f7c048094deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b90104015d8eb900000000000000000000000000000000000000000000000000000000009e1c4a0000000000000000000000000000000000000000000000000000000065a11748000000000000000000000000000000000000000000000000000000000000000a4b479e5fa8d52dd20a8a66e468b56e993bdbffcccf729223aabff06299ab36db000000000000000000000000000000000000000000000000000000000000000400000000000000000000000073b4168cc87f35cc239200a20eb841cded23493b000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240"]}"#;
5099 let _payload = serde_json::from_str::<ExecutionPayloadInputV2>(payload).unwrap();
5100 }
5101
5102 #[test]
5103 fn roundtrip_payload_to_block() {
5104 let first_transaction_raw = Bytes::from_static(&hex!("02f9017a8501a1f0ff438211cc85012a05f2008512a05f2000830249f094d5409474fd5a725eab2ac9a8b26ca6fb51af37ef80b901040cc7326300000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000001bdd2ed4b616c800000000000000000000000000001e9ee781dd4b97bdef92e5d1785f73a1f931daa20000000000000000000000007a40026a3b9a41754a95eec8c92c6b99886f440c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000009ae80eb647dd09968488fa1d7e412bf8558a0b7a0000000000000000000000000f9815537d361cb02befd9918c95c97d4d8a4a2bc001a0ba8f1928bb0efc3fcd01524a2039a9a2588fa567cd9a7cc18217e05c615e9d69a0544bfd11425ac7748e76b3795b57a5563e2b0eff47b5428744c62ff19ccfc305")[..]);
5105 let second_transaction_raw = Bytes::from_static(&hex!("03f901388501a1f0ff430c843b9aca00843b9aca0082520894e7249813d8ccf6fa95a2203f46a64166073d58878080c005f8c6a00195f6dff17753fc89b60eac6477026a805116962c9e412de8015c0484e661c1a001aae314061d4f5bbf158f15d9417a238f9589783f58762cd39d05966b3ba2fba0013f5be9b12e7da06f0dd11a7bdc4e0db8ef33832acc23b183bd0a2c1408a757a0019d9ac55ea1a615d92965e04d960cb3be7bff121a381424f1f22865bd582e09a001def04412e76df26fefe7b0ed5e10580918ae4f355b074c0cfe5d0259157869a0011c11a415db57e43db07aef0de9280b591d65ca0cce36c7002507f8191e5d4a80a0c89b59970b119187d97ad70539f1624bbede92648e2dc007890f9658a88756c5a06fb2e3d4ce2c438c0856c2de34948b7032b1aadc4642a9666228ea8cdc7786b7")[..]);
5106
5107 let new_payload = ExecutionPayloadV3 {
5108 payload_inner: ExecutionPayloadV2 {
5109 payload_inner: ExecutionPayloadV1 {
5110 base_fee_per_gas: U256::from(7u64),
5111 block_number: 0xa946u64,
5112 block_hash: hex!("a5ddd3f286f429458a39cafc13ffe89295a7efa8eb363cf89a1a4887dbcf272b").into(),
5113 logs_bloom: hex!("00200004000000000000000080000000000200000000000000000000000000000000200000000000000000000000000000000000800000000200000000000000000000000000000000000008000000200000000000000000000001000000000000000000000000000000800000000000000000000100000000000030000000000000000040000000000000000000000000000000000800080080404000000000000008000000000008200000000000200000000000000000000000000000000000000002000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000100000000000000000000").into(),
5114 extra_data: hex!("d883010d03846765746888676f312e32312e31856c696e7578").into(),
5115 gas_limit: 0x1c9c380,
5116 gas_used: 0x1f4a9,
5117 timestamp: 0x651f35b8,
5118 fee_recipient: hex!("f97e180c050e5ab072211ad2c213eb5aee4df134").into(),
5119 parent_hash: hex!("d829192799c73ef28a7332313b3c03af1f2d5da2c36f8ecfafe7a83a3bfb8d1e").into(),
5120 prev_randao: hex!("753888cc4adfbeb9e24e01c84233f9d204f4a9e1273f0e29b43c4c148b2b8b7e").into(),
5121 receipts_root: hex!("4cbc48e87389399a0ea0b382b1c46962c4b8e398014bf0cc610f9c672bee3155").into(),
5122 state_root: hex!("017d7fa2b5adb480f5e05b2c95cb4186e12062eed893fc8822798eed134329d1").into(),
5123 transactions: vec![first_transaction_raw, second_transaction_raw],
5124 },
5125 withdrawals: vec![],
5126 },
5127 blob_gas_used: 0xc0000,
5128 excess_blob_gas: 0x580000,
5129 };
5130
5131 let mut block: Block<TxEnvelope> = new_payload.clone().try_into_block().unwrap();
5132
5133 let parent_beacon_block_root =
5136 b256!("531cd53b8e68deef0ea65edfa3cda927a846c307b0907657af34bc3f313b5871");
5137 block.header.parent_beacon_block_root = Some(parent_beacon_block_root);
5138
5139 let converted_payload = ExecutionPayloadV3::from_block_unchecked(block.hash_slow(), &block);
5140
5141 assert_eq!(new_payload, converted_payload);
5143 }
5144
5145 #[test]
5146 fn payload_to_block_rejects_network_encoded_tx() {
5147 let first_transaction_raw = Bytes::from_static(&hex!("b9017e02f9017a8501a1f0ff438211cc85012a05f2008512a05f2000830249f094d5409474fd5a725eab2ac9a8b26ca6fb51af37ef80b901040cc7326300000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000001bdd2ed4b616c800000000000000000000000000001e9ee781dd4b97bdef92e5d1785f73a1f931daa20000000000000000000000007a40026a3b9a41754a95eec8c92c6b99886f440c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000009ae80eb647dd09968488fa1d7e412bf8558a0b7a0000000000000000000000000f9815537d361cb02befd9918c95c97d4d8a4a2bc001a0ba8f1928bb0efc3fcd01524a2039a9a2588fa567cd9a7cc18217e05c615e9d69a0544bfd11425ac7748e76b3795b57a5563e2b0eff47b5428744c62ff19ccfc305")[..]);
5148 let second_transaction_raw = Bytes::from_static(&hex!("b9013c03f901388501a1f0ff430c843b9aca00843b9aca0082520894e7249813d8ccf6fa95a2203f46a64166073d58878080c005f8c6a00195f6dff17753fc89b60eac6477026a805116962c9e412de8015c0484e661c1a001aae314061d4f5bbf158f15d9417a238f9589783f58762cd39d05966b3ba2fba0013f5be9b12e7da06f0dd11a7bdc4e0db8ef33832acc23b183bd0a2c1408a757a0019d9ac55ea1a615d92965e04d960cb3be7bff121a381424f1f22865bd582e09a001def04412e76df26fefe7b0ed5e10580918ae4f355b074c0cfe5d0259157869a0011c11a415db57e43db07aef0de9280b591d65ca0cce36c7002507f8191e5d4a80a0c89b59970b119187d97ad70539f1624bbede92648e2dc007890f9658a88756c5a06fb2e3d4ce2c438c0856c2de34948b7032b1aadc4642a9666228ea8cdc7786b7")[..]);
5149
5150 let new_payload = ExecutionPayloadV3 {
5151 payload_inner: ExecutionPayloadV2 {
5152 payload_inner: ExecutionPayloadV1 {
5153 base_fee_per_gas: U256::from(7u64),
5154 block_number: 0xa946u64,
5155 block_hash: hex!("a5ddd3f286f429458a39cafc13ffe89295a7efa8eb363cf89a1a4887dbcf272b").into(),
5156 logs_bloom: hex!("00200004000000000000000080000000000200000000000000000000000000000000200000000000000000000000000000000000800000000200000000000000000000000000000000000008000000200000000000000000000001000000000000000000000000000000800000000000000000000100000000000030000000000000000040000000000000000000000000000000000800080080404000000000000008000000000008200000000000200000000000000000000000000000000000000002000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000100000000000000000000").into(),
5157 extra_data: hex!("d883010d03846765746888676f312e32312e31856c696e7578").into(),
5158 gas_limit: 0x1c9c380,
5159 gas_used: 0x1f4a9,
5160 timestamp: 0x651f35b8,
5161 fee_recipient: hex!("f97e180c050e5ab072211ad2c213eb5aee4df134").into(),
5162 parent_hash: hex!("d829192799c73ef28a7332313b3c03af1f2d5da2c36f8ecfafe7a83a3bfb8d1e").into(),
5163 prev_randao: hex!("753888cc4adfbeb9e24e01c84233f9d204f4a9e1273f0e29b43c4c148b2b8b7e").into(),
5164 receipts_root: hex!("4cbc48e87389399a0ea0b382b1c46962c4b8e398014bf0cc610f9c672bee3155").into(),
5165 state_root: hex!("017d7fa2b5adb480f5e05b2c95cb4186e12062eed893fc8822798eed134329d1").into(),
5166 transactions: vec![first_transaction_raw, second_transaction_raw],
5167 },
5168 withdrawals: vec![],
5169 },
5170 blob_gas_used: 0xc0000,
5171 excess_blob_gas: 0x580000,
5172 };
5173
5174 let _block = new_payload
5175 .try_into_block::<TxEnvelope>()
5176 .expect_err("execution payload conversion requires typed txs without a rlp header");
5177 }
5178
5179 #[test]
5180 fn devnet_invalid_block_hash_repro() {
5181 let deser_block = r#"
5182 {
5183 "parentHash": "0xae8315ee86002e6269a17dd1e9516a6cf13223e9d4544d0c32daff826fb31acc",
5184 "feeRecipient": "0xf97e180c050e5ab072211ad2c213eb5aee4df134",
5185 "stateRoot": "0x03787f1579efbaa4a8234e72465eb4e29ef7e62f61242d6454661932e1a282a1",
5186 "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
5187 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
5188 "prevRandao": "0x918e86b497dc15de7d606457c36ca583e24d9b0a110a814de46e33d5bb824a66",
5189 "blockNumber": "0x6a784",
5190 "gasLimit": "0x1c9c380",
5191 "gasUsed": "0x0",
5192 "timestamp": "0x65bc1d60",
5193 "extraData": "0x9a726574682f76302e312e302d616c7068612e31362f6c696e7578",
5194 "baseFeePerGas": "0x8",
5195 "blobGasUsed": "0x0",
5196 "excessBlobGas": "0x0",
5197 "blockHash": "0x340c157eca9fd206b87c17f0ecbe8d411219de7188a0a240b635c88a96fe91c5",
5198 "transactions": [],
5199 "withdrawals": [
5200 {
5201 "index": "0x5ab202",
5202 "validatorIndex": "0xb1b",
5203 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5204 "amount": "0x19b3d"
5205 },
5206 {
5207 "index": "0x5ab203",
5208 "validatorIndex": "0xb1c",
5209 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5210 "amount": "0x15892"
5211 },
5212 {
5213 "index": "0x5ab204",
5214 "validatorIndex": "0xb1d",
5215 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5216 "amount": "0x19b3d"
5217 },
5218 {
5219 "index": "0x5ab205",
5220 "validatorIndex": "0xb1e",
5221 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5222 "amount": "0x19b3d"
5223 },
5224 {
5225 "index": "0x5ab206",
5226 "validatorIndex": "0xb1f",
5227 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5228 "amount": "0x19b3d"
5229 },
5230 {
5231 "index": "0x5ab207",
5232 "validatorIndex": "0xb20",
5233 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5234 "amount": "0x19b3d"
5235 },
5236 {
5237 "index": "0x5ab208",
5238 "validatorIndex": "0xb21",
5239 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5240 "amount": "0x15892"
5241 },
5242 {
5243 "index": "0x5ab209",
5244 "validatorIndex": "0xb22",
5245 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5246 "amount": "0x19b3d"
5247 },
5248 {
5249 "index": "0x5ab20a",
5250 "validatorIndex": "0xb23",
5251 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5252 "amount": "0x19b3d"
5253 },
5254 {
5255 "index": "0x5ab20b",
5256 "validatorIndex": "0xb24",
5257 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5258 "amount": "0x17db2"
5259 },
5260 {
5261 "index": "0x5ab20c",
5262 "validatorIndex": "0xb25",
5263 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5264 "amount": "0x19b3d"
5265 },
5266 {
5267 "index": "0x5ab20d",
5268 "validatorIndex": "0xb26",
5269 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5270 "amount": "0x19b3d"
5271 },
5272 {
5273 "index": "0x5ab20e",
5274 "validatorIndex": "0xa91",
5275 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5276 "amount": "0x15892"
5277 },
5278 {
5279 "index": "0x5ab20f",
5280 "validatorIndex": "0xa92",
5281 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5282 "amount": "0x1c05d"
5283 },
5284 {
5285 "index": "0x5ab210",
5286 "validatorIndex": "0xa93",
5287 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5288 "amount": "0x15892"
5289 },
5290 {
5291 "index": "0x5ab211",
5292 "validatorIndex": "0xa94",
5293 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5294 "amount": "0x19b3d"
5295 }
5296 ]
5297 }
5298 "#;
5299
5300 let payload: ExecutionPayload =
5302 serde_json::from_str::<ExecutionPayloadV3>(deser_block).unwrap().into();
5303
5304 let block_hash_with_blob_fee_fields =
5308 b256!("a7cdd5f9e54147b53a15833a8c45dffccbaed534d7fdc23458f45102a4bf71b0");
5309
5310 let versioned_hashes = vec![];
5311 let parent_beacon_block_root =
5312 b256!("1162de8a0f4d20d86b9ad6e0a2575ab60f00a433dc70d9318c8abc9041fddf54");
5313
5314 let cancun_fields = CancunPayloadFields { parent_beacon_block_root, versioned_hashes };
5316
5317 let block = payload
5319 .try_into_block_with_sidecar::<TxEnvelope>(&ExecutionPayloadSidecar::v3(cancun_fields))
5320 .unwrap();
5321
5322 assert_eq!(block_hash_with_blob_fee_fields, block.header.hash_slow());
5324 }
5325
5326 #[test]
5327 fn test_payload_to_block_with_sidecar_raw() {
5328 use std::path::PathBuf;
5329
5330 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/payload");
5331 let dir = std::fs::read_dir(path).expect("Unable to read payload folder");
5332
5333 for entry in dir {
5334 let entry = entry.expect("Unable to read entry");
5335 let path = entry.path();
5336
5337 if path.extension().and_then(|s| s.to_str()) != Some("json") {
5338 continue;
5339 }
5340
5341 let contents = std::fs::read_to_string(&path).expect("Unable to read file");
5342 let value: serde_json::Value = serde_json::from_str(&contents)
5343 .unwrap_or_else(|e| panic!("Failed to parse JSON from {path:?}: {e}"));
5344
5345 let new_payload = &value["newPayload"];
5347 let payload_value = &new_payload["payload"];
5348 let sidecar_value = &new_payload["sidecar"];
5349
5350 let payload: ExecutionPayload = serde_json::from_value(payload_value.clone())
5351 .unwrap_or_else(|e| panic!("Failed to deserialize payload from {path:?}: {e}"));
5352
5353 let sidecar: ExecutionPayloadSidecar = serde_json::from_value(sidecar_value.clone())
5355 .unwrap_or_else(|e| panic!("Failed to deserialize sidecar from {path:?}: {e}"));
5356
5357 let block = payload.clone().into_block_with_sidecar_raw(&sidecar).unwrap_or_else(|e| {
5359 panic!("Failed to convert payload to block from {path:?}: {e}")
5360 });
5361
5362 if let Some(tx_count) = payload_value["transactions"].as_array().map(|a| a.len()) {
5364 assert_eq!(
5365 block.body.transactions.len(),
5366 tx_count,
5367 "Transaction count mismatch in {:?}",
5368 path
5369 );
5370 }
5371
5372 assert_eq!(
5374 block.header.parent_beacon_block_root,
5375 sidecar.parent_beacon_block_root(),
5376 "Parent beacon block root mismatch in {:?}",
5377 path
5378 );
5379 assert_eq!(
5380 block.header.requests_hash,
5381 sidecar.requests_hash(),
5382 "Requests hash mismatch in {:?}",
5383 path
5384 );
5385
5386 let expected_hash = payload_value["blockHash"]
5388 .as_str()
5389 .unwrap()
5390 .parse::<B256>()
5391 .unwrap_or_else(|e| panic!("Failed to parse block hash from {path:?}: {e}"));
5392 let actual_hash = block.header.hash_slow();
5393 assert_eq!(
5394 actual_hash, expected_hash,
5395 "Block hash mismatch in {:?}: expected {}, got {}",
5396 path, expected_hash, actual_hash
5397 );
5398
5399 let block =
5400 payload.try_into_block_with_sidecar::<TxEnvelope>(&sidecar).unwrap_or_else(|e| {
5401 panic!("Failed to convert payload to block from {path:?}: {e}")
5402 });
5403 let actual_hash = block.header.hash_slow();
5404 assert_eq!(
5405 actual_hash, expected_hash,
5406 "Block hash mismatch in {:?}: expected {}, got {}",
5407 path, expected_hash, actual_hash
5408 );
5409 }
5410 }
5411
5412 #[test]
5413 #[cfg(feature = "serde")]
5414 fn test_into_block_raw_with_transactions_root() {
5415 use std::path::PathBuf;
5416
5417 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/payload");
5418 let dir = std::fs::read_dir(path).expect("Unable to read payload folder");
5419
5420 for entry in dir {
5421 let entry = entry.expect("Unable to read entry");
5422 let path = entry.path();
5423
5424 if path.extension().and_then(|s| s.to_str()) != Some("json") {
5425 continue;
5426 }
5427
5428 let contents = std::fs::read_to_string(&path).expect("Unable to read file");
5429 let value: serde_json::Value = serde_json::from_str(&contents)
5430 .unwrap_or_else(|e| panic!("Failed to parse JSON from {path:?}: {e}"));
5431
5432 let new_payload = &value["newPayload"];
5433 let payload_value = &new_payload["payload"];
5434 let sidecar_value = &new_payload["sidecar"];
5435
5436 let payload: ExecutionPayload = serde_json::from_value(payload_value.clone())
5437 .unwrap_or_else(|e| panic!("Failed to deserialize payload from {path:?}: {e}"));
5438
5439 let sidecar: ExecutionPayloadSidecar = serde_json::from_value(sidecar_value.clone())
5440 .unwrap_or_else(|e| panic!("Failed to deserialize sidecar from {path:?}: {e}"));
5441
5442 let expected_hash = payload_value["blockHash"]
5443 .as_str()
5444 .unwrap()
5445 .parse::<B256>()
5446 .unwrap_or_else(|e| panic!("Failed to parse block hash from {path:?}: {e}"));
5447
5448 let block_normal =
5450 payload.clone().into_block_with_sidecar_raw(&sidecar).unwrap_or_else(|e| {
5451 panic!("Failed to convert payload to block from {path:?}: {e}")
5452 });
5453 let tx_root = block_normal.header.transactions_root;
5454
5455 let block_with_root =
5457 payload.clone().into_block_raw_with_transactions_root(tx_root).unwrap();
5458 assert_eq!(
5459 block_with_root.header.transactions_root, tx_root,
5460 "transactions_root mismatch in {path:?}"
5461 );
5462
5463 let block_opt_some =
5465 payload.clone().into_block_raw_with_transactions_root_opt(Some(tx_root)).unwrap();
5466 assert_eq!(
5467 block_opt_some.header.transactions_root, tx_root,
5468 "opt(Some) transactions_root mismatch in {path:?}"
5469 );
5470
5471 let block_opt_none =
5473 payload.clone().into_block_raw_with_transactions_root_opt(None).unwrap();
5474 assert_eq!(
5475 block_opt_none.header.transactions_root, tx_root,
5476 "opt(None) transactions_root mismatch in {path:?}"
5477 );
5478
5479 let block_sidecar_root = payload
5481 .into_block_with_sidecar_raw_with_transactions_root(&sidecar, tx_root)
5482 .unwrap();
5483 let actual_hash = block_sidecar_root.header.hash_slow();
5484 assert_eq!(
5485 actual_hash, expected_hash,
5486 "Block hash mismatch with pre-computed tx root in {path:?}"
5487 );
5488 }
5489 }
5490
5491 #[test]
5492 fn test_v1_with_transactions_root_override() {
5493 let transaction = Bytes::from_static(&hex!("f86d0a8458b20efd825208946177843db3138ae69679a54b95cf345ed759450d8806f3e8d87878800080820a95a0f8bddb1dcc4558b532ff747760a6f547dd275afdbe7bdecc90680e71de105757a014f34ba38c180913c0543b0ac2eccfb77cc3f801a535008dc50e533fbe435f53"));
5494
5495 let payload = ExecutionPayloadV1 {
5496 parent_hash: B256::default(),
5497 fee_recipient: Address::default(),
5498 state_root: B256::default(),
5499 receipts_root: B256::default(),
5500 logs_bloom: Bloom::default(),
5501 prev_randao: B256::default(),
5502 block_number: 0,
5503 gas_limit: 0,
5504 gas_used: 0,
5505 timestamp: 0,
5506 extra_data: Bytes::default(),
5507 base_fee_per_gas: U256::from(1),
5508 block_hash: B256::default(),
5509 transactions: vec![transaction],
5510 };
5511
5512 let computed_root = payload.clone().into_block_raw().unwrap().header.transactions_root;
5513
5514 let fake_root = b256!("1111111111111111111111111111111111111111111111111111111111111111");
5515 assert_ne!(computed_root, fake_root);
5516
5517 let block = payload.clone().into_block_raw_with_transactions_root(fake_root).unwrap();
5518 assert_eq!(block.header.transactions_root, fake_root);
5519
5520 let block_opt = payload.into_block_raw_with_transactions_root_opt(Some(fake_root)).unwrap();
5521 assert_eq!(block_opt.header.transactions_root, fake_root);
5522 }
5523
5524 #[test]
5525 fn test_with_transactions_root_extra_data_validation() {
5526 let payload = ExecutionPayloadV1 {
5527 parent_hash: B256::default(),
5528 fee_recipient: Address::default(),
5529 state_root: B256::default(),
5530 receipts_root: B256::default(),
5531 logs_bloom: Bloom::default(),
5532 prev_randao: B256::default(),
5533 block_number: 0,
5534 gas_limit: 0,
5535 gas_used: 0,
5536 timestamp: 0,
5537 extra_data: Bytes::from(vec![0u8; MAXIMUM_EXTRA_DATA_SIZE + 1]),
5538 base_fee_per_gas: U256::from(1),
5539 block_hash: B256::default(),
5540 transactions: vec![],
5541 };
5542
5543 let fake_root = b256!("1111111111111111111111111111111111111111111111111111111111111111");
5544
5545 assert!(payload.clone().into_block_raw().is_err());
5546 assert!(payload.clone().into_block_raw_with_transactions_root(fake_root).is_err());
5547 assert!(payload.into_block_raw_with_transactions_root_opt(Some(fake_root)).is_err());
5548 }
5549
5550 #[test]
5551 fn test_decoded_transactions() {
5552 let transaction = Bytes::from_static(&hex!("f86d0a8458b20efd825208946177843db3138ae69679a54b95cf345ed759450d8806f3e8d87878800080820a95a0f8bddb1dcc4558b532ff747760a6f547dd275afdbe7bdecc90680e71de105757a014f34ba38c180913c0543b0ac2eccfb77cc3f801a535008dc50e533fbe435f53"));
5553
5554 let payload = ExecutionPayload::V1(ExecutionPayloadV1 {
5555 parent_hash: B256::default(),
5556 fee_recipient: Address::default(),
5557 state_root: B256::default(),
5558 receipts_root: B256::default(),
5559 logs_bloom: Bloom::default(),
5560 prev_randao: B256::default(),
5561 block_number: 0,
5562 gas_limit: 0,
5563 gas_used: 0,
5564 timestamp: 0,
5565 extra_data: Bytes::default(),
5566 base_fee_per_gas: U256::default(),
5567 block_hash: B256::default(),
5568 transactions: vec![transaction.clone()],
5569 });
5570
5571 let decoded: Vec<_> = payload.decoded_transactions::<TxEnvelope>().collect();
5573 assert_eq!(decoded.len(), 1);
5574 assert!(decoded[0].is_ok(), "Failed to decode transaction: {:?}", decoded[0]);
5575
5576 let decoded_with_encoded: Vec<_> =
5578 payload.decoded_transactions_with_encoded::<TxEnvelope>().collect();
5579 assert_eq!(decoded_with_encoded.len(), 1);
5580 assert!(decoded_with_encoded[0].is_ok());
5581 if let Ok(with_encoded) = &decoded_with_encoded[0] {
5582 assert_eq!(with_encoded.encoded_bytes(), &transaction);
5583 }
5584 }
5585
5586 #[test]
5587 #[cfg(feature = "serde")]
5588 fn serde_payload_attributes_without_slot_number() {
5589 let json = r#"{
5590 "timestamp": "0x1234",
5591 "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000",
5592 "suggestedFeeRecipient": "0x0000000000000000000000000000000000000000"
5593 }"#;
5594
5595 let attrs: PayloadAttributes = serde_json::from_str(json).unwrap();
5596 assert_eq!(attrs.timestamp, 0x1234);
5597 assert!(attrs.slot_number.is_none());
5598 assert!(attrs.target_gas_limit.is_none());
5599 }
5600
5601 #[test]
5602 #[cfg(feature = "serde")]
5603 fn serde_payload_attributes_with_hex_amsterdam_fields() {
5604 let json = r#"{
5605 "timestamp": "0x2",
5606 "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000",
5607 "suggestedFeeRecipient": "0x0000000000000000000000000000000000000000",
5608 "withdrawals": [],
5609 "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
5610 "slotNumber": "0x0",
5611 "targetGasLimit": "0x1c9c380"
5612 }"#;
5613
5614 let attrs: PayloadAttributes = serde_json::from_str(json).unwrap();
5615 assert_eq!(attrs.timestamp, 0x2);
5616 assert_eq!(attrs.slot_number, Some(0));
5617 assert_eq!(attrs.target_gas_limit, Some(30_000_000));
5618 }
5619
5620 #[test]
5621 #[cfg(feature = "serde")]
5622 fn serde_execution_payload_body_v2() {
5623 let body = ExecutionPayloadBodyV2 {
5624 transactions: vec![Bytes::from(vec![0x01, 0x02, 0x03])],
5625 withdrawals: Some(vec![Withdrawal {
5626 index: 1,
5627 validator_index: 2,
5628 address: Address::default(),
5629 amount: 100,
5630 }]),
5631 block_access_list: Some(Bytes::from(vec![0xaa, 0xbb, 0xcc])),
5632 };
5633
5634 let serialized = serde_json::to_string(&body).unwrap();
5635 let deserialized: ExecutionPayloadBodyV2 = serde_json::from_str(&serialized).unwrap();
5636 assert_eq!(deserialized, body);
5637 }
5638
5639 #[test]
5640 #[cfg(feature = "serde")]
5641 fn serde_execution_payload_body_v2_null_fields() {
5642 let body = ExecutionPayloadBodyV2 {
5643 transactions: vec![],
5644 withdrawals: None,
5645 block_access_list: None,
5646 };
5647
5648 let serialized = serde_json::to_string(&body).unwrap();
5649 let deserialized: ExecutionPayloadBodyV2 = serde_json::from_str(&serialized).unwrap();
5650 assert_eq!(deserialized, body);
5651 }
5652
5653 #[test]
5654 #[cfg(feature = "ssz")]
5655 fn ssz_execution_payload_body_v1_roundtrip() {
5656 use ssz::{Decode, Encode};
5657
5658 let body = ExecutionPayloadBodyV1 {
5659 transactions: vec![Bytes::from(vec![0x01, 0x02, 0x03])],
5660 withdrawals: Some(vec![Withdrawal {
5661 index: 1,
5662 validator_index: 2,
5663 address: Address::with_last_byte(3),
5664 amount: 4,
5665 }]),
5666 };
5667
5668 let decoded = ExecutionPayloadBodyV1::from_ssz_bytes(&body.as_ssz_bytes()).unwrap();
5669 assert_eq!(decoded, body);
5670
5671 let bodies: ExecutionPayloadBodiesV1 = vec![Some(body), None];
5672 let decoded = ExecutionPayloadBodiesV1::from_ssz_bytes(&bodies.as_ssz_bytes()).unwrap();
5673 assert_eq!(decoded, bodies);
5674 }
5675
5676 #[test]
5677 #[cfg(feature = "ssz")]
5678 fn ssz_execution_payload_body_v2_roundtrip() {
5679 use ssz::{Decode, Encode};
5680
5681 let body = ExecutionPayloadBodyV2 {
5682 transactions: vec![Bytes::from(vec![0x04, 0x05, 0x06])],
5683 withdrawals: None,
5684 block_access_list: Some(Bytes::from(vec![0xaa, 0xbb, 0xcc])),
5685 };
5686
5687 let decoded = ExecutionPayloadBodyV2::from_ssz_bytes(&body.as_ssz_bytes()).unwrap();
5688 assert_eq!(decoded, body);
5689
5690 let bodies: ExecutionPayloadBodiesV2 = vec![Some(body), None];
5691 let decoded = ExecutionPayloadBodiesV2::from_ssz_bytes(&bodies.as_ssz_bytes()).unwrap();
5692 assert_eq!(decoded, bodies);
5693 }
5694
5695 #[test]
5696 fn execution_payload_body_v1_to_v2_conversion() {
5697 let v1 = ExecutionPayloadBodyV1 {
5698 transactions: vec![Bytes::from(vec![0x01, 0x02])],
5699 withdrawals: Some(vec![Withdrawal {
5700 index: 1,
5701 validator_index: 2,
5702 address: Address::default(),
5703 amount: 100,
5704 }]),
5705 };
5706
5707 let v2: ExecutionPayloadBodyV2 = v1.clone().into();
5708 assert_eq!(v2.transactions, v1.transactions);
5709 assert_eq!(v2.withdrawals, v1.withdrawals);
5710 assert_eq!(v2.block_access_list, None);
5711 }
5712
5713 #[test]
5714 fn execution_payload_body_v2_to_v1_conversion() {
5715 let v2 = ExecutionPayloadBodyV2 {
5716 transactions: vec![Bytes::from(vec![0x01, 0x02])],
5717 withdrawals: Some(vec![Withdrawal {
5718 index: 1,
5719 validator_index: 2,
5720 address: Address::default(),
5721 amount: 100,
5722 }]),
5723 block_access_list: Some(Bytes::from(vec![0xaa, 0xbb])),
5724 };
5725
5726 let v1: ExecutionPayloadBodyV1 = v2.clone().into();
5727 assert_eq!(v1.transactions, v2.transactions);
5728 assert_eq!(v1.withdrawals, v2.withdrawals);
5729 }
5730
5731 #[test]
5732 #[cfg(feature = "serde")]
5733 fn serde_roundtrip_payload_v2() {
5734 let payload = ExecutionPayloadV2 {
5735 payload_inner: ExecutionPayloadV1 {
5736 parent_hash: B256::default(),
5737 fee_recipient: Address::default(),
5738 state_root: B256::default(),
5739 receipts_root: B256::default(),
5740 logs_bloom: Bloom::default(),
5741 prev_randao: B256::default(),
5742 block_number: 1,
5743 gas_limit: 30_000_000,
5744 gas_used: 21000,
5745 timestamp: 1234,
5746 extra_data: Bytes::default(),
5747 base_fee_per_gas: U256::from(7u64),
5748 block_hash: B256::default(),
5749 transactions: vec![],
5750 },
5751 withdrawals: vec![Withdrawal {
5752 index: 1,
5753 validator_index: 2,
5754 address: Address::default(),
5755 amount: 100,
5756 }],
5757 };
5758
5759 let serialized = serde_json::to_string(&payload).unwrap();
5760 let deserialized: ExecutionPayloadV2 = serde_json::from_str(&serialized).unwrap();
5761 assert_eq!(payload, deserialized);
5762 }
5763
5764 #[test]
5765 #[cfg(feature = "serde")]
5766 fn serde_roundtrip_payload_v4() {
5767 let payload = ExecutionPayloadV4 {
5768 payload_inner: ExecutionPayloadV3 {
5769 payload_inner: ExecutionPayloadV2 {
5770 payload_inner: ExecutionPayloadV1 {
5771 parent_hash: B256::default(),
5772 fee_recipient: Address::default(),
5773 state_root: B256::default(),
5774 receipts_root: B256::default(),
5775 logs_bloom: Bloom::default(),
5776 prev_randao: B256::default(),
5777 block_number: 1,
5778 gas_limit: 30_000_000,
5779 gas_used: 21000,
5780 timestamp: 1234,
5781 extra_data: Bytes::default(),
5782 base_fee_per_gas: U256::from(7u64),
5783 block_hash: B256::default(),
5784 transactions: vec![],
5785 },
5786 withdrawals: vec![],
5787 },
5788 blob_gas_used: 0,
5789 excess_blob_gas: 0,
5790 },
5791 block_access_list: Bytes::from(vec![0xaa, 0xbb]),
5792 slot_number: 0,
5793 };
5794
5795 let serialized = serde_json::to_string(&payload).unwrap();
5796 let deserialized: ExecutionPayloadV4 = serde_json::from_str(&serialized).unwrap();
5797 assert_eq!(payload, deserialized);
5798 }
5799
5800 #[test]
5801 fn payload_v4_from_block_falls_back_to_bal_hash_bytes() {
5802 let bal_hash = b256!("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef");
5803 let header = Header {
5804 block_access_list_hash: Some(bal_hash),
5805 slot_number: Some(7),
5806 ..Default::default()
5807 };
5808
5809 let block: Block<TxEnvelope> = Block::new(header, BlockBody::default());
5810 let (payload, _) = ExecutionPayload::from_block_unchecked(B256::with_last_byte(1), &block);
5811
5812 let payload = payload.as_v4().expect("expected V4 payload");
5813 assert_eq!(payload.block_access_list, Bytes::copy_from_slice(bal_hash.as_slice()));
5814 assert_eq!(payload.slot_number, 7);
5815 }
5816
5817 #[test]
5818 fn payload_v4_from_block_without_bal_hash_uses_empty_bal_hash_bytes() {
5819 let header = Header { slot_number: Some(3), ..Default::default() };
5820
5821 let block: Block<TxEnvelope> = Block::new(header, BlockBody::default());
5822 let payload = ExecutionPayloadV4::from_block_unchecked(B256::with_last_byte(2), &block);
5823
5824 assert_eq!(
5825 payload.block_access_list,
5826 Bytes::copy_from_slice(EMPTY_BLOCK_ACCESS_LIST_HASH.as_slice())
5827 );
5828 assert_eq!(payload.slot_number, 3);
5829 }
5830
5831 #[test]
5832 fn execution_data_from_sealed_block_uses_sealed_hash() {
5833 let block: Block<TxEnvelope> = Block::new(Header::default(), BlockBody::default());
5834 let block_hash = B256::with_last_byte(3);
5835
5836 let execution_data = ExecutionData::from(Sealed::new_unchecked(block, block_hash));
5837
5838 assert_eq!(execution_data.block_hash(), block_hash);
5839 }
5840
5841 #[test]
5842 fn execution_data_from_sealed_block_ref_uses_sealed_hash() {
5843 let block: Block<TxEnvelope> = Block::new(Header::default(), BlockBody::default());
5844 let block_hash = B256::with_last_byte(4);
5845
5846 let execution_data = ExecutionData::from(Sealed::new_unchecked(&block, block_hash));
5847
5848 assert_eq!(execution_data.block_hash(), block_hash);
5849 }
5850
5851 #[test]
5852 fn execution_data_from_sealed_block_with_extras_preserves_bal() {
5853 let block_access_list = Bytes::from(vec![0xaa, 0xbb, 0xcc]);
5854 let header = Header {
5855 block_access_list_hash: Some(keccak256(&block_access_list)),
5856 slot_number: Some(7),
5857 ..Default::default()
5858 };
5859
5860 let block: Block<TxEnvelope> = Block::new(header, BlockBody::default());
5861 let block_hash = B256::with_last_byte(5);
5862 let execution_data = ExecutionData::from((
5863 Sealed::new_unchecked(block, block_hash),
5864 PayloadExtras::from(block_access_list.clone()),
5865 ));
5866
5867 assert_eq!(execution_data.block_hash(), block_hash);
5868 assert_eq!(execution_data.payload.block_access_list(), Some(&block_access_list));
5869 assert_eq!(execution_data.payload.slot_number(), Some(7));
5870 }
5871
5872 #[test]
5873 fn execution_data_from_sealed_block_ref_with_extras_preserves_bal() {
5874 let block_access_list = Bytes::from(vec![0xaa, 0xbb, 0xcc]);
5875 let header = Header {
5876 block_access_list_hash: Some(keccak256(&block_access_list)),
5877 slot_number: Some(7),
5878 ..Default::default()
5879 };
5880
5881 let block: Block<TxEnvelope> = Block::new(header, BlockBody::default());
5882 let block_hash = B256::with_last_byte(6);
5883 let execution_data = ExecutionData::from((
5884 Sealed::new_unchecked(&block, block_hash),
5885 PayloadExtras::from(block_access_list.clone()),
5886 ));
5887
5888 assert_eq!(execution_data.block_hash(), block_hash);
5889 assert_eq!(execution_data.payload.block_access_list(), Some(&block_access_list));
5890 assert_eq!(execution_data.payload.slot_number(), Some(7));
5891 }
5892
5893 #[test]
5894 fn execution_payload_gets_bal_hash_and_slot_number_from_v4() {
5895 let block_access_list = Bytes::from(vec![0xaa, 0xbb, 0xcc]);
5896 let payload = ExecutionPayload::from(ExecutionPayloadV4 {
5897 payload_inner: ExecutionPayloadV3 {
5898 payload_inner: ExecutionPayloadV2 {
5899 payload_inner: ExecutionPayloadV1 {
5900 parent_hash: B256::default(),
5901 fee_recipient: Address::default(),
5902 state_root: B256::default(),
5903 receipts_root: B256::default(),
5904 logs_bloom: Bloom::default(),
5905 prev_randao: B256::default(),
5906 block_number: 1,
5907 gas_limit: 30_000_000,
5908 gas_used: 21_000,
5909 timestamp: 1_234,
5910 extra_data: Bytes::default(),
5911 base_fee_per_gas: U256::ZERO,
5912 block_hash: B256::default(),
5913 transactions: vec![],
5914 },
5915 withdrawals: vec![],
5916 },
5917 blob_gas_used: 0,
5918 excess_blob_gas: 0,
5919 },
5920 block_access_list: block_access_list.clone(),
5921 slot_number: 7,
5922 });
5923
5924 assert_eq!(payload.slot_number(), Some(7));
5925 assert_eq!(payload.bal_hash(), Some(keccak256(&block_access_list)));
5926 }
5927
5928 #[test]
5929 #[cfg(feature = "serde")]
5930 fn serde_roundtrip_payload_input_v2_with_withdrawals() {
5931 let payload = ExecutionPayloadInputV2 {
5932 execution_payload: ExecutionPayloadV1 {
5933 parent_hash: B256::default(),
5934 fee_recipient: Address::default(),
5935 state_root: B256::default(),
5936 receipts_root: B256::default(),
5937 logs_bloom: Bloom::default(),
5938 prev_randao: B256::default(),
5939 block_number: 1,
5940 gas_limit: 30_000_000,
5941 gas_used: 21000,
5942 timestamp: 1234,
5943 extra_data: Bytes::default(),
5944 base_fee_per_gas: U256::from(7u64),
5945 block_hash: B256::default(),
5946 transactions: vec![],
5947 },
5948 withdrawals: Some(vec![]),
5949 };
5950
5951 let serialized = serde_json::to_string(&payload).unwrap();
5952 let deserialized: ExecutionPayloadInputV2 = serde_json::from_str(&serialized).unwrap();
5953 assert_eq!(payload, deserialized);
5954 }
5955
5956 #[test]
5957 #[cfg(feature = "serde")]
5958 fn serde_roundtrip_payload_input_v2_without_withdrawals() {
5959 let payload = ExecutionPayloadInputV2 {
5960 execution_payload: ExecutionPayloadV1 {
5961 parent_hash: B256::default(),
5962 fee_recipient: Address::default(),
5963 state_root: B256::default(),
5964 receipts_root: B256::default(),
5965 logs_bloom: Bloom::default(),
5966 prev_randao: B256::default(),
5967 block_number: 1,
5968 gas_limit: 30_000_000,
5969 gas_used: 21000,
5970 timestamp: 1234,
5971 extra_data: Bytes::default(),
5972 base_fee_per_gas: U256::from(7u64),
5973 block_hash: B256::default(),
5974 transactions: vec![],
5975 },
5976 withdrawals: None,
5977 };
5978
5979 let serialized = serde_json::to_string(&payload).unwrap();
5980 let deserialized: ExecutionPayloadInputV2 = serde_json::from_str(&serialized).unwrap();
5981 assert_eq!(payload, deserialized);
5982 }
5983
5984 #[test]
5985 #[cfg(feature = "serde")]
5986 fn serde_roundtrip_envelope_v4() {
5987 let envelope = ExecutionPayloadEnvelopeV4 {
5988 envelope_inner: ExecutionPayloadEnvelopeV3 {
5989 execution_payload: ExecutionPayloadV3 {
5990 payload_inner: ExecutionPayloadV2 {
5991 payload_inner: ExecutionPayloadV1 {
5992 parent_hash: B256::default(),
5993 fee_recipient: Address::default(),
5994 state_root: B256::default(),
5995 receipts_root: B256::default(),
5996 logs_bloom: Bloom::default(),
5997 prev_randao: B256::default(),
5998 block_number: 1,
5999 gas_limit: 30_000_000,
6000 gas_used: 21000,
6001 timestamp: 1234,
6002 extra_data: Bytes::default(),
6003 base_fee_per_gas: U256::from(7u64),
6004 block_hash: B256::default(),
6005 transactions: vec![],
6006 },
6007 withdrawals: vec![],
6008 },
6009 blob_gas_used: 0,
6010 excess_blob_gas: 0,
6011 },
6012 block_value: U256::from(1u64),
6013 blobs_bundle: BlobsBundleV1::empty(),
6014 should_override_builder: false,
6015 },
6016 execution_requests: Default::default(),
6017 };
6018
6019 let serialized = serde_json::to_string(&envelope).unwrap();
6020 let deserialized: ExecutionPayloadEnvelopeV4 = serde_json::from_str(&serialized).unwrap();
6021 assert_eq!(envelope, deserialized);
6022 }
6023
6024 #[test]
6025 #[cfg(feature = "serde")]
6026 fn serde_v3_with_many_transactions() {
6027 let tx = Bytes::from_static(&hex!("f865808506fc23ac00830124f8940000000000000000000000000000000000000316018032a044b25a8b9b247d01586b3d59c71728ff49c9b84928d9e7fa3377ead3b5570b5da03ceac696601ff7ee6f5fe8864e2998db9babdf5eeba1a0cd5b4d44b3fcbd181b"));
6028 let transactions: Vec<Bytes> = (0..100).map(|_| tx.clone()).collect();
6029
6030 let payload = ExecutionPayloadV3 {
6031 payload_inner: ExecutionPayloadV2 {
6032 payload_inner: ExecutionPayloadV1 {
6033 parent_hash: B256::default(),
6034 fee_recipient: Address::default(),
6035 state_root: B256::default(),
6036 receipts_root: B256::default(),
6037 logs_bloom: Bloom::default(),
6038 prev_randao: B256::default(),
6039 block_number: 1,
6040 gas_limit: 30_000_000,
6041 gas_used: 2_100_000,
6042 timestamp: 1234,
6043 extra_data: Bytes::default(),
6044 base_fee_per_gas: U256::from(7u64),
6045 block_hash: B256::default(),
6046 transactions,
6047 },
6048 withdrawals: vec![],
6049 },
6050 blob_gas_used: 0,
6051 excess_blob_gas: 0,
6052 };
6053
6054 let serialized = serde_json::to_string(&payload).unwrap();
6055 let deserialized: ExecutionPayloadV3 = serde_json::from_str(&serialized).unwrap();
6056 assert_eq!(payload, deserialized);
6057 }
6058
6059 #[test]
6060 #[cfg(feature = "serde")]
6061 fn serde_input_v2_rejects_unknown_fields() {
6062 let input = r#"{
6063 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
6064 "feeRecipient": "0x0000000000000000000000000000000000000000",
6065 "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
6066 "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
6067 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
6068 "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000",
6069 "blockNumber": "0x1",
6070 "gasLimit": "0x1c9c380",
6071 "gasUsed": "0x0",
6072 "timestamp": "0x1235",
6073 "extraData": "0x",
6074 "baseFeePerGas": "0x7",
6075 "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
6076 "transactions": [],
6077 "unknownField": "should fail"
6078 }"#;
6079
6080 let result: Result<ExecutionPayloadInputV2, _> = serde_json::from_str(input);
6081 assert!(result.is_err());
6082 }
6083}