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 #[serde(deserialize_with = "alloy_eips::eip4844::deserialize_blobs")]
1883 blobs: Vec<alloy_consensus::Blob>,
1884 }
1885 let raw = BlobsBundleRaw::deserialize(deserializer)?;
1886
1887 if raw.proofs.len() == raw.commitments.len() && raw.proofs.len() == raw.blobs.len() {
1888 Ok(Self { commitments: raw.commitments, proofs: raw.proofs, blobs: raw.blobs })
1889 } else {
1890 Err(serde::de::Error::invalid_length(
1891 raw.proofs.len(),
1892 &format!("{}", raw.commitments.len()).as_str(),
1893 ))
1894 }
1895 }
1896}
1897
1898impl BlobsBundleV1 {
1899 pub fn new(sidecars: impl IntoIterator<Item = BlobTransactionSidecar>) -> Self {
1903 let (commitments, proofs, blobs) = sidecars.into_iter().fold(
1904 (Vec::new(), Vec::new(), Vec::new()),
1905 |(mut commitments, mut proofs, mut blobs), sidecar| {
1906 commitments.extend(sidecar.commitments);
1907 proofs.extend(sidecar.proofs);
1908 blobs.extend(sidecar.blobs);
1909 (commitments, proofs, blobs)
1910 },
1911 );
1912 Self { commitments, proofs, blobs }
1913 }
1914
1915 pub fn empty() -> Self {
1920 Self::default()
1921 }
1922
1923 pub fn versioned_hashes(&self) -> Vec<B256> {
1925 self.commitments
1926 .iter()
1927 .map(|c| alloy_eips::eip4844::kzg_to_versioned_hash(c.as_slice()))
1928 .collect()
1929 }
1930
1931 pub fn take(&mut self, len: usize) -> (Vec<Bytes48>, Vec<Bytes48>, Vec<Blob>) {
1937 (
1938 self.commitments.drain(0..len).collect(),
1939 self.proofs.drain(0..len).collect(),
1940 self.blobs.drain(0..len).collect(),
1941 )
1942 }
1943
1944 pub fn pop_sidecar(&mut self, len: usize) -> BlobTransactionSidecar {
1950 let (commitments, proofs, blobs) = self.take(len);
1951 BlobTransactionSidecar { commitments, proofs, blobs }
1952 }
1953
1954 #[cfg(feature = "kzg")]
1961 pub fn try_into_sidecar(
1962 self,
1963 ) -> Result<BlobTransactionSidecar, alloy_consensus::error::ValueError<Self>> {
1964 if self.commitments.len() != self.proofs.len() || self.commitments.len() != self.blobs.len()
1965 {
1966 return Err(alloy_consensus::error::ValueError::new(self, "length mismatch"));
1967 }
1968
1969 let Self { commitments, proofs, blobs } = self;
1970 Ok(BlobTransactionSidecar { blobs, commitments, proofs })
1971 }
1972
1973 #[cfg(feature = "kzg")]
1982 pub fn try_into_v2(self) -> Result<BlobsBundleV2, alloy_eips::eip4844::c_kzg::Error> {
1983 self.try_into_v2_with_settings(
1984 alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
1985 )
1986 }
1987
1988 #[cfg(feature = "kzg")]
1995 pub fn try_into_v2_with_settings(
1996 self,
1997 settings: &alloy_eips::eip4844::c_kzg::KzgSettings,
1998 ) -> Result<BlobsBundleV2, alloy_eips::eip4844::c_kzg::Error> {
1999 use alloy_eips::eip7594::CELLS_PER_EXT_BLOB;
2000
2001 if let [blob] = self.blobs.as_slice() {
2002 let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob.as_ckzg())?;
2003 let cell_proofs =
2004 alloy_eips::eip4844::c_kzg::KzgProof::boxed_slice_as_alloy(kzg_proofs).into();
2005 return Ok(BlobsBundleV2 {
2006 commitments: self.commitments,
2007 proofs: cell_proofs,
2008 blobs: self.blobs,
2009 });
2010 }
2011
2012 let mut cell_proofs = Vec::with_capacity(self.blobs.len() * CELLS_PER_EXT_BLOB);
2013
2014 for blob in self.blobs.iter() {
2015 let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob.as_ckzg())?;
2017 cell_proofs.extend_from_slice(alloy_eips::eip4844::c_kzg::KzgProof::slice_as_alloy(
2018 kzg_proofs.as_ref(),
2019 ));
2020 }
2021
2022 Ok(BlobsBundleV2 { commitments: self.commitments, proofs: cell_proofs, blobs: self.blobs })
2023 }
2024}
2025
2026impl From<Vec<BlobTransactionSidecar>> for BlobsBundleV1 {
2027 fn from(sidecars: Vec<BlobTransactionSidecar>) -> Self {
2028 Self::new(sidecars)
2029 }
2030}
2031
2032impl FromIterator<BlobTransactionSidecar> for BlobsBundleV1 {
2033 fn from_iter<T: IntoIterator<Item = BlobTransactionSidecar>>(iter: T) -> Self {
2034 Self::new(iter)
2035 }
2036}
2037
2038#[cfg(feature = "kzg")]
2039impl TryFrom<BlobsBundleV1> for BlobTransactionSidecar {
2040 type Error = alloy_consensus::error::ValueError<BlobsBundleV1>;
2041
2042 fn try_from(value: BlobsBundleV1) -> Result<Self, Self::Error> {
2043 value.try_into_sidecar()
2044 }
2045}
2046
2047#[cfg(feature = "kzg")]
2048impl TryFrom<BlobsBundleV1> for BlobsBundleV2 {
2049 type Error = alloy_eips::eip4844::c_kzg::Error;
2050
2051 fn try_from(value: BlobsBundleV1) -> Result<Self, Self::Error> {
2052 value.try_into_v2()
2053 }
2054}
2055
2056#[derive(Clone, Debug, Default, PartialEq, Eq)]
2058#[cfg_attr(feature = "serde", derive(serde::Serialize))]
2059#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode))]
2060#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
2061pub struct BlobsBundleV2 {
2062 pub commitments: Vec<alloy_consensus::Bytes48>,
2064 pub proofs: Vec<alloy_consensus::Bytes48>,
2066 pub blobs: Vec<alloy_consensus::Blob>,
2068}
2069
2070#[cfg(feature = "serde")]
2071impl<'de> serde::Deserialize<'de> for BlobsBundleV2 {
2072 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2073 where
2074 D: serde::Deserializer<'de>,
2075 {
2076 #[derive(serde::Deserialize)]
2077 struct BlobsBundleRaw {
2078 commitments: Vec<alloy_consensus::Bytes48>,
2079 proofs: Vec<alloy_consensus::Bytes48>,
2080 #[serde(deserialize_with = "alloy_eips::eip4844::deserialize_blobs")]
2081 blobs: Vec<alloy_consensus::Blob>,
2082 }
2083 let raw = BlobsBundleRaw::deserialize(deserializer)?;
2084
2085 if raw.proofs.len() == raw.blobs.len() * CELLS_PER_EXT_BLOB
2086 && raw.commitments.len() == raw.blobs.len()
2087 {
2088 Ok(Self { commitments: raw.commitments, proofs: raw.proofs, blobs: raw.blobs })
2089 } else {
2090 Err(serde::de::Error::invalid_length(
2091 raw.proofs.len(),
2092 &format!("{}", raw.commitments.len() * CELLS_PER_EXT_BLOB).as_str(),
2093 ))
2094 }
2095 }
2096}
2097
2098#[cfg(feature = "ssz")]
2099impl ssz::Decode for BlobsBundleV2 {
2100 fn is_ssz_fixed_len() -> bool {
2101 false
2102 }
2103
2104 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
2105 #[derive(ssz_derive::Decode)]
2106 struct BlobsBundleRaw {
2107 commitments: Vec<alloy_consensus::Bytes48>,
2108 proofs: Vec<alloy_consensus::Bytes48>,
2109 blobs: Vec<alloy_consensus::Blob>,
2110 }
2111
2112 let raw = BlobsBundleRaw::from_ssz_bytes(bytes)?;
2113
2114 if raw.proofs.len() == raw.blobs.len() * CELLS_PER_EXT_BLOB
2115 && raw.commitments.len() == raw.blobs.len()
2116 {
2117 Ok(Self { commitments: raw.commitments, proofs: raw.proofs, blobs: raw.blobs })
2118 } else {
2119 Err(ssz::DecodeError::BytesInvalid(
2120 format!(
2121 "Invalid BlobsBundleV2: expected {} proofs and {} commitments for {} blobs, got {} proofs and {} commitments",
2122 raw.blobs.len() * CELLS_PER_EXT_BLOB,
2123 raw.blobs.len(),
2124 raw.blobs.len(),
2125 raw.proofs.len(),
2126 raw.commitments.len()
2127 )
2128 ))
2129 }
2130 }
2131}
2132
2133impl BlobsBundleV2 {
2134 pub fn new(sidecars: impl IntoIterator<Item = BlobTransactionSidecarEip7594>) -> Self {
2138 let (commitments, proofs, blobs) = sidecars.into_iter().fold(
2139 (Vec::new(), Vec::new(), Vec::new()),
2140 |(mut commitments, mut proofs, mut blobs), sidecar| {
2141 commitments.extend(sidecar.commitments);
2142 proofs.extend(sidecar.cell_proofs);
2143 blobs.extend(sidecar.blobs);
2144 (commitments, proofs, blobs)
2145 },
2146 );
2147 Self { commitments, proofs, blobs }
2148 }
2149
2150 pub fn empty() -> Self {
2155 Self::default()
2156 }
2157
2158 pub fn versioned_hashes(&self) -> Vec<B256> {
2160 self.commitments
2161 .iter()
2162 .map(|c| alloy_eips::eip4844::kzg_to_versioned_hash(c.as_slice()))
2163 .collect()
2164 }
2165
2166 pub fn take(&mut self, len: usize) -> (Vec<Bytes48>, Vec<Bytes48>, Vec<Blob>) {
2174 (
2175 self.commitments.drain(0..len).collect(),
2176 self.proofs.drain(0..len * CELLS_PER_EXT_BLOB).collect(),
2177 self.blobs.drain(0..len).collect(),
2178 )
2179 }
2180
2181 pub fn pop_sidecar(&mut self, len: usize) -> BlobTransactionSidecarEip7594 {
2187 let (commitments, cell_proofs, blobs) = self.take(len);
2188 BlobTransactionSidecarEip7594 { commitments, cell_proofs, blobs }
2189 }
2190
2191 #[cfg(feature = "kzg")]
2199 pub fn try_into_sidecar(
2200 self,
2201 ) -> Result<BlobTransactionSidecarEip7594, alloy_consensus::error::ValueError<Self>> {
2202 let expected_cell_proofs_len = self.blobs.len() * CELLS_PER_EXT_BLOB;
2203 if self.proofs.len() != expected_cell_proofs_len {
2204 let msg = format!(
2205 "cell proofs length mismatch, expected {expected_cell_proofs_len}, has {}",
2206 self.proofs.len()
2207 );
2208 return Err(alloy_consensus::error::ValueError::new(self, msg));
2209 }
2210
2211 if self.commitments.len() != self.blobs.len() {
2212 let msg = format!(
2213 "commitments length ({}) mismatch, expected blob length ({})",
2214 self.commitments.len(),
2215 self.blobs.len()
2216 );
2217 return Err(alloy_consensus::error::ValueError::new(self, msg));
2218 }
2219
2220 let Self { commitments, proofs, blobs } = self;
2221 Ok(BlobTransactionSidecarEip7594 { blobs, commitments, cell_proofs: proofs })
2222 }
2223
2224 #[cfg(feature = "kzg")]
2233 pub fn try_into_v1(self) -> Result<BlobsBundleV1, alloy_eips::eip4844::c_kzg::Error> {
2234 self.try_into_v1_with_settings(
2235 alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
2236 )
2237 }
2238
2239 #[cfg(feature = "kzg")]
2249 pub fn try_into_v1_with_settings(
2250 self,
2251 settings: &alloy_eips::eip4844::c_kzg::KzgSettings,
2252 ) -> Result<BlobsBundleV1, alloy_eips::eip4844::c_kzg::Error> {
2253 let mut proofs = Vec::with_capacity(self.blobs.len());
2254
2255 for (blob, commitment) in self.blobs.iter().zip(self.commitments.iter()) {
2256 let proof = settings.compute_blob_kzg_proof(blob.as_ckzg(), commitment.as_ckzg())?;
2258
2259 proofs.push(Bytes48::from_ckzg(proof.to_bytes()));
2260 }
2261
2262 Ok(BlobsBundleV1 { commitments: self.commitments, proofs, blobs: self.blobs })
2263 }
2264}
2265
2266impl From<Vec<BlobTransactionSidecarEip7594>> for BlobsBundleV2 {
2267 fn from(sidecars: Vec<BlobTransactionSidecarEip7594>) -> Self {
2268 Self::new(sidecars)
2269 }
2270}
2271
2272impl FromIterator<BlobTransactionSidecarEip7594> for BlobsBundleV2 {
2273 fn from_iter<T: IntoIterator<Item = BlobTransactionSidecarEip7594>>(iter: T) -> Self {
2274 Self::new(iter)
2275 }
2276}
2277
2278#[cfg(feature = "kzg")]
2279impl TryFrom<BlobsBundleV2> for BlobTransactionSidecarEip7594 {
2280 type Error = alloy_consensus::error::ValueError<BlobsBundleV2>;
2281
2282 fn try_from(value: BlobsBundleV2) -> Result<Self, Self::Error> {
2283 value.try_into_sidecar()
2284 }
2285}
2286
2287#[cfg(feature = "kzg")]
2288impl TryFrom<BlobsBundleV2> for BlobsBundleV1 {
2289 type Error = alloy_eips::eip4844::c_kzg::Error;
2290
2291 fn try_from(value: BlobsBundleV2) -> Result<Self, Self::Error> {
2292 value.try_into_v1()
2293 }
2294}
2295
2296#[derive(Clone, Debug, PartialEq, Eq)]
2303#[cfg_attr(feature = "serde", derive(serde::Serialize))]
2304#[cfg_attr(feature = "serde", serde(untagged))]
2305#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
2306pub enum ExecutionPayload {
2307 V1(ExecutionPayloadV1),
2309 V2(ExecutionPayloadV2),
2311 V3(ExecutionPayloadV3),
2313 V4(ExecutionPayloadV4),
2315}
2316
2317impl ExecutionPayload {
2318 pub fn from_block_slow<T, H>(block: &Block<T, H>) -> (Self, ExecutionPayloadSidecar)
2326 where
2327 T: Encodable2718 + Transaction,
2328 H: BlockHeader + Sealable,
2329 {
2330 Self::from_block_unchecked(block.hash_slow(), block)
2331 }
2332
2333 pub fn from_block_slow_with_bal<T, H>(
2343 block: &Block<T, H>,
2344 block_access_list: Bytes,
2345 ) -> (Self, ExecutionPayloadSidecar)
2346 where
2347 T: Encodable2718 + Transaction,
2348 H: BlockHeader + Sealable,
2349 {
2350 Self::from_block_slow_with_extras(block, block_access_list)
2351 }
2352
2353 pub fn from_block_slow_with_extras<T, H>(
2363 block: &Block<T, H>,
2364 extras: impl Into<PayloadExtras>,
2365 ) -> (Self, ExecutionPayloadSidecar)
2366 where
2367 T: Encodable2718 + Transaction,
2368 H: BlockHeader + Sealable,
2369 {
2370 let extras = extras.into();
2371 if let Some(block_access_list) = extras.bal {
2372 Self::from_block_unchecked_with_bal(block.hash_slow(), block, block_access_list)
2373 } else {
2374 Self::from_block_unchecked(block.hash_slow(), block)
2375 }
2376 }
2377
2378 pub fn from_block_unchecked<T, H>(
2394 block_hash: B256,
2395 block: &Block<T, H>,
2396 ) -> (Self, ExecutionPayloadSidecar)
2397 where
2398 T: Encodable2718 + Transaction,
2399 H: BlockHeader,
2400 {
2401 let sidecar = ExecutionPayloadSidecar::from_block(block);
2402
2403 let execution_payload = if block.header.block_access_list_hash().is_some() {
2404 Self::V4(ExecutionPayloadV4::from_block_unchecked(block_hash, block))
2406 } else if block.header.parent_beacon_block_root().is_some() {
2407 Self::V3(ExecutionPayloadV3::from_block_unchecked(block_hash, block))
2409 } else if block.body.withdrawals.is_some() {
2410 Self::V2(ExecutionPayloadV2::from_block_unchecked(block_hash, block))
2412 } else {
2413 Self::V1(ExecutionPayloadV1::from_block_unchecked(block_hash, block))
2415 };
2416
2417 (execution_payload, sidecar)
2418 }
2419
2420 pub fn from_block_unchecked_with_bal<T, H>(
2428 block_hash: B256,
2429 block: &Block<T, H>,
2430 block_access_list: Bytes,
2431 ) -> (Self, ExecutionPayloadSidecar)
2432 where
2433 T: Encodable2718 + Transaction,
2434 H: BlockHeader,
2435 {
2436 let sidecar = ExecutionPayloadSidecar::from_block(block);
2437
2438 let execution_payload = if block.header.block_access_list_hash().is_some() {
2439 Self::V4(ExecutionPayloadV4::from_block_unchecked_with_bal(
2441 block_hash,
2442 block,
2443 block_access_list,
2444 ))
2445 } else if block.header.parent_beacon_block_root().is_some() {
2446 Self::V3(ExecutionPayloadV3::from_block_unchecked(block_hash, block))
2448 } else if block.body.withdrawals.is_some() {
2449 Self::V2(ExecutionPayloadV2::from_block_unchecked(block_hash, block))
2451 } else {
2452 Self::V1(ExecutionPayloadV1::from_block_unchecked(block_hash, block))
2454 };
2455
2456 (execution_payload, sidecar)
2457 }
2458
2459 pub fn from_block_unchecked_with_extras<T, H>(
2467 block_hash: B256,
2468 block: &Block<T, H>,
2469 extras: impl Into<PayloadExtras>,
2470 ) -> (Self, ExecutionPayloadSidecar)
2471 where
2472 T: Encodable2718 + Transaction,
2473 H: BlockHeader,
2474 {
2475 let extras = extras.into();
2476 if let Some(block_access_list) = extras.bal {
2477 Self::from_block_unchecked_with_bal(block_hash, block, block_access_list)
2478 } else {
2479 Self::from_block_unchecked(block_hash, block)
2480 }
2481 }
2482
2483 pub fn try_into_block_with_sidecar<T: Decodable2718>(
2494 self,
2495 sidecar: &ExecutionPayloadSidecar,
2496 ) -> Result<Block<T>, PayloadError> {
2497 self.try_into_block_with_sidecar_with(sidecar, |tx| {
2498 T::decode_2718_exact(tx.as_ref())
2499 .map_err(alloy_rlp::Error::from)
2500 .map_err(PayloadError::from)
2501 })
2502 }
2503
2504 pub fn try_into_block_with_sidecar_with<T, F, E>(
2510 self,
2511 sidecar: &ExecutionPayloadSidecar,
2512 f: F,
2513 ) -> Result<Block<T>, PayloadError>
2514 where
2515 F: FnMut(Bytes) -> Result<T, E>,
2516 E: Into<PayloadError>,
2517 {
2518 self.into_block_with_sidecar_raw(sidecar)?.try_map_transactions(f).map_err(Into::into)
2519 }
2520
2521 pub fn into_block_with_sidecar_raw(
2526 self,
2527 sidecar: &ExecutionPayloadSidecar,
2528 ) -> Result<Block<Bytes>, PayloadError> {
2529 let mut base_block = self.into_block_raw()?;
2530 base_block.header.parent_beacon_block_root = sidecar.parent_beacon_block_root();
2531 base_block.header.requests_hash = sidecar.requests_hash();
2532 Ok(base_block)
2533 }
2534
2535 pub fn try_into_block<T: Decodable2718>(self) -> Result<Block<T>, PayloadError> {
2547 self.try_into_block_with(|tx| {
2548 T::decode_2718_exact(tx.as_ref())
2549 .map_err(alloy_rlp::Error::from)
2550 .map_err(PayloadError::from)
2551 })
2552 }
2553
2554 pub fn try_into_block_with<T, F, E>(self, f: F) -> Result<Block<T>, PayloadError>
2563 where
2564 F: FnMut(Bytes) -> Result<T, E>,
2565 E: Into<PayloadError>,
2566 {
2567 self.into_block_raw()?.try_map_transactions(f).map_err(Into::into)
2568 }
2569
2570 pub fn into_block_raw(self) -> Result<Block<Bytes>, PayloadError> {
2575 self.into_block_raw_with_transactions_root_opt(None)
2576 }
2577
2578 pub fn into_block_raw_with_transactions_root(
2583 self,
2584 transactions_root: B256,
2585 ) -> Result<Block<Bytes>, PayloadError> {
2586 self.into_block_raw_with_transactions_root_opt(Some(transactions_root))
2587 }
2588
2589 pub fn into_block_raw_with_transactions_root_opt(
2594 self,
2595 transactions_root: Option<B256>,
2596 ) -> Result<Block<Bytes>, PayloadError> {
2597 match self {
2598 Self::V1(payload) => {
2599 payload.into_block_raw_with_transactions_root_opt(transactions_root)
2600 }
2601 Self::V2(payload) => {
2602 payload.into_block_raw_with_transactions_root_opt(transactions_root)
2603 }
2604 Self::V3(payload) => {
2605 payload.into_block_raw_with_transactions_root_opt(transactions_root)
2606 }
2607 Self::V4(payload) => {
2608 payload.into_block_raw_with_transactions_root_opt(transactions_root)
2609 }
2610 }
2611 }
2612
2613 pub fn into_block_with_sidecar_raw_with_transactions_root(
2618 self,
2619 sidecar: &ExecutionPayloadSidecar,
2620 transactions_root: B256,
2621 ) -> Result<Block<Bytes>, PayloadError> {
2622 let mut base_block = self.into_block_raw_with_transactions_root(transactions_root)?;
2623 base_block.header.parent_beacon_block_root = sidecar.parent_beacon_block_root();
2624 base_block.header.requests_hash = sidecar.requests_hash();
2625 Ok(base_block)
2626 }
2627
2628 pub const fn as_v1(&self) -> &ExecutionPayloadV1 {
2630 match self {
2631 Self::V1(payload) => payload,
2632 Self::V2(payload) => &payload.payload_inner,
2633 Self::V3(payload) => &payload.payload_inner.payload_inner,
2634 Self::V4(payload) => &payload.payload_inner.payload_inner.payload_inner,
2635 }
2636 }
2637
2638 pub const fn as_v1_mut(&mut self) -> &mut ExecutionPayloadV1 {
2640 match self {
2641 Self::V1(payload) => payload,
2642 Self::V2(payload) => &mut payload.payload_inner,
2643 Self::V3(payload) => &mut payload.payload_inner.payload_inner,
2644 Self::V4(payload) => &mut payload.payload_inner.payload_inner.payload_inner,
2645 }
2646 }
2647
2648 pub fn into_v1(self) -> ExecutionPayloadV1 {
2650 match self {
2651 Self::V1(payload) => payload,
2652 Self::V2(payload) => payload.payload_inner,
2653 Self::V3(payload) => payload.payload_inner.payload_inner,
2654 Self::V4(payload) => payload.payload_inner.payload_inner.payload_inner,
2655 }
2656 }
2657
2658 pub const fn as_v2(&self) -> Option<&ExecutionPayloadV2> {
2660 match self {
2661 Self::V1(_) => None,
2662 Self::V2(payload) => Some(payload),
2663 Self::V3(payload) => Some(&payload.payload_inner),
2664 Self::V4(payload) => Some(&payload.payload_inner.payload_inner),
2665 }
2666 }
2667
2668 pub const fn as_v2_mut(&mut self) -> Option<&mut ExecutionPayloadV2> {
2670 match self {
2671 Self::V1(_) => None,
2672 Self::V2(payload) => Some(payload),
2673 Self::V3(payload) => Some(&mut payload.payload_inner),
2674 Self::V4(payload) => Some(&mut payload.payload_inner.payload_inner),
2675 }
2676 }
2677
2678 pub const fn as_v3(&self) -> Option<&ExecutionPayloadV3> {
2680 match self {
2681 Self::V1(_) | Self::V2(_) => None,
2682 Self::V3(payload) => Some(payload),
2683 Self::V4(payload) => Some(&payload.payload_inner),
2684 }
2685 }
2686
2687 pub const fn as_v3_mut(&mut self) -> Option<&mut ExecutionPayloadV3> {
2689 match self {
2690 Self::V1(_) | Self::V2(_) => None,
2691 Self::V3(payload) => Some(payload),
2692 Self::V4(payload) => Some(&mut payload.payload_inner),
2693 }
2694 }
2695
2696 pub const fn as_v4(&self) -> Option<&ExecutionPayloadV4> {
2698 match self {
2699 Self::V1(_) | Self::V2(_) | Self::V3(_) => None,
2700 Self::V4(payload) => Some(payload),
2701 }
2702 }
2703
2704 pub const fn as_v4_mut(&mut self) -> Option<&mut ExecutionPayloadV4> {
2706 match self {
2707 Self::V1(_) | Self::V2(_) | Self::V3(_) => None,
2708 Self::V4(payload) => Some(payload),
2709 }
2710 }
2711
2712 pub const fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
2714 match self.as_v2() {
2715 Some(payload) => Some(&payload.withdrawals),
2716 None => None,
2717 }
2718 }
2719
2720 pub const fn transactions(&self) -> &Vec<Bytes> {
2722 &self.as_v1().transactions
2723 }
2724
2725 pub const fn transactions_mut(&mut self) -> &mut Vec<Bytes> {
2727 &mut self.as_v1_mut().transactions
2728 }
2729
2730 pub fn header_info(&self) -> HeaderInfo {
2732 HeaderInfo {
2733 number: self.block_number(),
2734 beneficiary: self.fee_recipient(),
2735 timestamp: self.timestamp(),
2736 gas_limit: self.gas_limit(),
2737 base_fee_per_gas: Some(self.saturated_base_fee_per_gas()),
2738 excess_blob_gas: self.excess_blob_gas(),
2739 blob_gas_used: self.blob_gas_used(),
2740 difficulty: U256::ZERO,
2741 mix_hash: Some(self.prev_randao()),
2742 slot_number: self.as_v4().map(|payload| payload.slot_number),
2743 }
2744 }
2745
2746 pub fn saturated_base_fee_per_gas(&self) -> u64 {
2750 self.as_v1().base_fee_per_gas.saturating_to()
2751 }
2752
2753 pub fn blob_gas_used(&self) -> Option<u64> {
2755 self.as_v3().map(|payload| payload.blob_gas_used)
2756 }
2757
2758 pub fn excess_blob_gas(&self) -> Option<u64> {
2760 self.as_v3().map(|payload| payload.excess_blob_gas)
2761 }
2762
2763 pub fn block_access_list(&self) -> Option<&Bytes> {
2767 self.as_v4().map(|payload| &payload.block_access_list)
2768 }
2769
2770 pub fn bal_hash(&self) -> Option<B256> {
2774 self.as_v4().map(|payload| keccak256(&payload.block_access_list))
2775 }
2776
2777 pub fn slot_number(&self) -> Option<u64> {
2781 self.as_v4().map(|payload| payload.slot_number)
2782 }
2783
2784 pub const fn gas_limit(&self) -> u64 {
2786 self.as_v1().gas_limit
2787 }
2788
2789 pub const fn fee_recipient(&self) -> Address {
2791 self.as_v1().fee_recipient
2792 }
2793
2794 pub const fn timestamp(&self) -> u64 {
2796 self.as_v1().timestamp
2797 }
2798
2799 pub const fn parent_hash(&self) -> B256 {
2801 self.as_v1().parent_hash
2802 }
2803
2804 pub const fn block_hash(&self) -> B256 {
2806 self.as_v1().block_hash
2807 }
2808
2809 pub const fn block_number(&self) -> u64 {
2811 self.as_v1().block_number
2812 }
2813
2814 pub const fn block_num_hash(&self) -> BlockNumHash {
2816 self.as_v1().block_num_hash()
2817 }
2818
2819 pub const fn prev_randao(&self) -> B256 {
2821 self.as_v1().prev_randao
2822 }
2823
2824 pub fn blob_fee(&self, blob_params: BlobParams) -> Option<u128> {
2828 Some(blob_params.calc_blob_fee(self.excess_blob_gas()?))
2829 }
2830
2831 pub fn next_block_blob_fee(&self, blob_params: BlobParams) -> Option<u128> {
2837 Some(blob_params.calc_blob_fee(self.next_block_excess_blob_gas(blob_params)?))
2838 }
2839
2840 pub fn next_block_base_fee(&self, base_fee_params: BaseFeeParams) -> Option<u64> {
2844 self.as_v1().next_block_base_fee(base_fee_params)
2845 }
2846
2847 pub fn next_block_excess_blob_gas(&self, blob_params: BlobParams) -> Option<u64> {
2852 Some(blob_params.next_block_excess_blob_gas_osaka(
2853 self.excess_blob_gas()?,
2854 self.blob_gas_used()?,
2855 self.as_v1().base_fee_per_gas.to(),
2856 ))
2857 }
2858
2859 pub fn maybe_next_block_excess_blob_gas(&self, blob_params: Option<BlobParams>) -> Option<u64> {
2864 self.next_block_excess_blob_gas(blob_params?)
2865 }
2866
2867 pub fn decoded_transactions<T: Decodable2718>(
2871 &self,
2872 ) -> impl Iterator<Item = Eip2718Result<T>> + '_ {
2873 self.transactions().iter().map(|tx_bytes| T::decode_2718_exact(tx_bytes.as_ref()))
2874 }
2875
2876 pub fn decoded_transactions_with_encoded<T: Decodable2718>(
2880 &self,
2881 ) -> impl Iterator<Item = Eip2718Result<WithEncoded<T>>> + '_ {
2882 self.transactions().iter().map(|tx_bytes| {
2883 T::decode_2718_exact(tx_bytes.as_ref()).map(|tx| WithEncoded::new(tx_bytes.clone(), tx))
2884 })
2885 }
2886
2887 pub fn recovered_transactions<T>(
2891 &self,
2892 ) -> impl Iterator<
2893 Item = Result<
2894 alloy_consensus::transaction::Recovered<T>,
2895 alloy_consensus::crypto::RecoveryError,
2896 >,
2897 > + '_
2898 where
2899 T: Decodable2718 + alloy_consensus::transaction::SignerRecoverable,
2900 {
2901 self.decoded_transactions::<T>().map(|res| {
2902 res.map_err(alloy_consensus::crypto::RecoveryError::from_source)
2903 .and_then(|tx| tx.try_into_recovered())
2904 })
2905 }
2906
2907 pub fn recovered_transactions_with_encoded<T>(
2913 &self,
2914 ) -> impl Iterator<
2915 Item = Result<
2916 WithEncoded<alloy_consensus::transaction::Recovered<T>>,
2917 alloy_consensus::crypto::RecoveryError,
2918 >,
2919 > + '_
2920 where
2921 T: Decodable2718 + alloy_consensus::transaction::SignerRecoverable,
2922 {
2923 self.transactions().iter().map(|tx_bytes| {
2924 T::decode_2718_exact(tx_bytes.as_ref())
2925 .map_err(alloy_consensus::crypto::RecoveryError::from_source)
2926 .and_then(|tx| {
2927 tx.try_into_recovered()
2928 .map(|recovered| WithEncoded::new(tx_bytes.clone(), recovered))
2929 })
2930 })
2931 }
2932
2933 #[doc(hidden)]
2935 pub const fn set_parent_hash(&mut self, parent_hash: B256) {
2936 self.as_v1_mut().parent_hash = parent_hash;
2937 }
2938
2939 #[doc(hidden)]
2941 pub const fn set_fee_recipient(&mut self, fee_recipient: Address) {
2942 self.as_v1_mut().fee_recipient = fee_recipient;
2943 }
2944
2945 #[doc(hidden)]
2947 pub const fn set_state_root(&mut self, state_root: B256) {
2948 self.as_v1_mut().state_root = state_root;
2949 }
2950
2951 #[doc(hidden)]
2953 pub const fn set_receipts_root(&mut self, receipts_root: B256) {
2954 self.as_v1_mut().receipts_root = receipts_root;
2955 }
2956
2957 #[doc(hidden)]
2959 pub const fn set_logs_bloom(&mut self, logs_bloom: Bloom) {
2960 self.as_v1_mut().logs_bloom = logs_bloom;
2961 }
2962
2963 #[doc(hidden)]
2965 pub const fn set_prev_randao(&mut self, prev_randao: B256) {
2966 self.as_v1_mut().prev_randao = prev_randao;
2967 }
2968
2969 #[doc(hidden)]
2971 pub const fn set_block_number(&mut self, block_number: u64) {
2972 self.as_v1_mut().block_number = block_number;
2973 }
2974
2975 #[doc(hidden)]
2977 pub const fn set_gas_limit(&mut self, gas_limit: u64) {
2978 self.as_v1_mut().gas_limit = gas_limit;
2979 }
2980
2981 #[doc(hidden)]
2983 pub const fn set_gas_used(&mut self, gas_used: u64) {
2984 self.as_v1_mut().gas_used = gas_used;
2985 }
2986
2987 #[doc(hidden)]
2989 pub const fn set_timestamp(&mut self, timestamp: u64) {
2990 self.as_v1_mut().timestamp = timestamp;
2991 }
2992
2993 #[doc(hidden)]
2995 pub fn set_extra_data(&mut self, extra_data: Bytes) {
2996 self.as_v1_mut().extra_data = extra_data;
2997 }
2998
2999 #[doc(hidden)]
3001 pub const fn set_base_fee_per_gas(&mut self, base_fee_per_gas: U256) {
3002 self.as_v1_mut().base_fee_per_gas = base_fee_per_gas;
3003 }
3004
3005 #[doc(hidden)]
3007 pub const fn set_block_hash(&mut self, block_hash: B256) {
3008 self.as_v1_mut().block_hash = block_hash;
3009 }
3010
3011 #[doc(hidden)]
3015 pub fn set_withdrawals(&mut self, withdrawals: Vec<Withdrawal>) -> bool {
3016 match self.as_v2_mut() {
3017 Some(payload) => {
3018 payload.withdrawals = withdrawals;
3019 true
3020 }
3021 None => false,
3022 }
3023 }
3024
3025 #[doc(hidden)]
3029 pub const fn set_blob_gas_used(&mut self, blob_gas_used: u64) -> bool {
3030 match self.as_v3_mut() {
3031 Some(payload) => {
3032 payload.blob_gas_used = blob_gas_used;
3033 true
3034 }
3035 None => false,
3036 }
3037 }
3038
3039 #[doc(hidden)]
3043 pub const fn set_excess_blob_gas(&mut self, excess_blob_gas: u64) -> bool {
3044 match self.as_v3_mut() {
3045 Some(payload) => {
3046 payload.excess_blob_gas = excess_blob_gas;
3047 true
3048 }
3049 None => false,
3050 }
3051 }
3052}
3053
3054impl From<ExecutionPayloadV1> for ExecutionPayload {
3055 fn from(payload: ExecutionPayloadV1) -> Self {
3056 Self::V1(payload)
3057 }
3058}
3059
3060impl From<ExecutionPayloadV2> for ExecutionPayload {
3061 fn from(payload: ExecutionPayloadV2) -> Self {
3062 Self::V2(payload)
3063 }
3064}
3065
3066impl From<ExecutionPayloadFieldV2> for ExecutionPayload {
3067 fn from(payload: ExecutionPayloadFieldV2) -> Self {
3068 payload.into_payload()
3069 }
3070}
3071
3072impl From<ExecutionPayloadV3> for ExecutionPayload {
3073 fn from(payload: ExecutionPayloadV3) -> Self {
3074 Self::V3(payload)
3075 }
3076}
3077
3078impl From<ExecutionPayloadV4> for ExecutionPayload {
3079 fn from(payload: ExecutionPayloadV4) -> Self {
3080 Self::V4(payload)
3081 }
3082}
3083
3084impl<T: Decodable2718> TryFrom<ExecutionPayload> for Block<T> {
3085 type Error = PayloadError;
3086
3087 fn try_from(value: ExecutionPayload) -> Result<Self, Self::Error> {
3088 value.try_into_block()
3089 }
3090}
3091
3092#[cfg(feature = "serde")]
3094impl<'de> serde::Deserialize<'de> for ExecutionPayload {
3095 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3096 where
3097 D: serde::Deserializer<'de>,
3098 {
3099 use alloy_primitives::U64;
3100
3101 struct ExecutionPayloadVisitor;
3102
3103 impl<'de> serde::de::Visitor<'de> for ExecutionPayloadVisitor {
3104 type Value = ExecutionPayload;
3105
3106 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3107 formatter.write_str("a valid ExecutionPayload object")
3108 }
3109
3110 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
3111 where
3112 A: serde::de::MapAccess<'de>,
3113 {
3114 #[cfg_attr(feature = "serde", derive(serde::Deserialize))]
3116 #[cfg_attr(feature = "serde", serde(field_identifier, rename_all = "camelCase"))]
3117 enum Fields {
3118 ParentHash,
3119 FeeRecipient,
3120 StateRoot,
3121 ReceiptsRoot,
3122 LogsBloom,
3123 PrevRandao,
3124 BlockNumber,
3125 GasLimit,
3126 GasUsed,
3127 Timestamp,
3128 ExtraData,
3129 BaseFeePerGas,
3130 BlockHash,
3131 Transactions,
3132 Withdrawals,
3134 BlobGasUsed,
3136 ExcessBlobGas,
3137 BlockAccessList,
3139 SlotNumber,
3140 }
3141
3142 let mut parent_hash = None;
3143 let mut fee_recipient = None;
3144 let mut state_root = None;
3145 let mut receipts_root = None;
3146 let mut logs_bloom = None;
3147 let mut prev_randao = None;
3148 let mut block_number = None;
3149 let mut gas_limit = None;
3150 let mut gas_used = None;
3151 let mut timestamp = None;
3152 let mut extra_data = None;
3153 let mut base_fee_per_gas = None;
3154 let mut block_hash = None;
3155 let mut transactions = None;
3156 let mut withdrawals = None;
3157 let mut blob_gas_used = None;
3158 let mut excess_blob_gas = None;
3159 let mut block_access_list = None;
3160 let mut slot_number = None;
3161
3162 while let Some(key) = map.next_key()? {
3163 match key {
3164 Fields::ParentHash => parent_hash = Some(map.next_value()?),
3165 Fields::FeeRecipient => fee_recipient = Some(map.next_value()?),
3166 Fields::StateRoot => state_root = Some(map.next_value()?),
3167 Fields::ReceiptsRoot => receipts_root = Some(map.next_value()?),
3168 Fields::LogsBloom => logs_bloom = Some(map.next_value()?),
3169 Fields::PrevRandao => prev_randao = Some(map.next_value()?),
3170 Fields::BlockNumber => {
3171 let raw = map.next_value::<U64>()?;
3172 block_number = Some(raw.to());
3173 }
3174 Fields::GasLimit => {
3175 let raw = map.next_value::<U64>()?;
3176 gas_limit = Some(raw.to());
3177 }
3178 Fields::GasUsed => {
3179 let raw = map.next_value::<U64>()?;
3180 gas_used = Some(raw.to());
3181 }
3182 Fields::Timestamp => {
3183 let raw = map.next_value::<U64>()?;
3184 timestamp = Some(raw.to());
3185 }
3186 Fields::ExtraData => extra_data = Some(map.next_value()?),
3187 Fields::BaseFeePerGas => base_fee_per_gas = Some(map.next_value()?),
3188 Fields::BlockHash => block_hash = Some(map.next_value()?),
3189 Fields::Transactions => transactions = Some(map.next_value()?),
3190 Fields::Withdrawals => withdrawals = Some(map.next_value()?),
3191 Fields::BlobGasUsed => {
3192 let raw = map.next_value::<U64>()?;
3193 blob_gas_used = Some(raw.to());
3194 }
3195 Fields::ExcessBlobGas => {
3196 let raw = map.next_value::<U64>()?;
3197 excess_blob_gas = Some(raw.to());
3198 }
3199 Fields::BlockAccessList => {
3200 block_access_list = Some(map.next_value()?);
3201 }
3202 Fields::SlotNumber => {
3203 let raw = map.next_value::<U64>()?;
3204 slot_number = Some(raw.to());
3205 }
3206 }
3207 }
3208
3209 let parent_hash =
3210 parent_hash.ok_or_else(|| serde::de::Error::missing_field("parentHash"))?;
3211 let fee_recipient =
3212 fee_recipient.ok_or_else(|| serde::de::Error::missing_field("feeRecipient"))?;
3213 let state_root =
3214 state_root.ok_or_else(|| serde::de::Error::missing_field("stateRoot"))?;
3215 let receipts_root =
3216 receipts_root.ok_or_else(|| serde::de::Error::missing_field("receiptsRoot"))?;
3217 let logs_bloom =
3218 logs_bloom.ok_or_else(|| serde::de::Error::missing_field("logsBloom"))?;
3219 let prev_randao =
3220 prev_randao.ok_or_else(|| serde::de::Error::missing_field("prevRandao"))?;
3221 let block_number =
3222 block_number.ok_or_else(|| serde::de::Error::missing_field("blockNumber"))?;
3223 let gas_limit =
3224 gas_limit.ok_or_else(|| serde::de::Error::missing_field("gasLimit"))?;
3225 let gas_used =
3226 gas_used.ok_or_else(|| serde::de::Error::missing_field("gasUsed"))?;
3227 let timestamp =
3228 timestamp.ok_or_else(|| serde::de::Error::missing_field("timestamp"))?;
3229 let extra_data =
3230 extra_data.ok_or_else(|| serde::de::Error::missing_field("extraData"))?;
3231 let base_fee_per_gas = base_fee_per_gas
3232 .ok_or_else(|| serde::de::Error::missing_field("baseFeePerGas"))?;
3233 let block_hash =
3234 block_hash.ok_or_else(|| serde::de::Error::missing_field("blockHash"))?;
3235 let transactions =
3236 transactions.ok_or_else(|| serde::de::Error::missing_field("transactions"))?;
3237
3238 let v1 = ExecutionPayloadV1 {
3239 parent_hash,
3240 fee_recipient,
3241 state_root,
3242 receipts_root,
3243 logs_bloom,
3244 prev_randao,
3245 block_number,
3246 gas_limit,
3247 gas_used,
3248 timestamp,
3249 extra_data,
3250 base_fee_per_gas,
3251 block_hash,
3252 transactions,
3253 };
3254
3255 let Some(withdrawals) = withdrawals else {
3256 return if blob_gas_used.is_none() && excess_blob_gas.is_none() {
3257 Ok(ExecutionPayload::V1(v1))
3258 } else {
3259 Err(serde::de::Error::custom("invalid enum variant"))
3260 };
3261 };
3262
3263 if let (Some(blob_gas_used), Some(excess_blob_gas)) =
3264 (blob_gas_used, excess_blob_gas)
3265 {
3266 let v3 = ExecutionPayloadV3 {
3267 payload_inner: ExecutionPayloadV2 { payload_inner: v1, withdrawals },
3268 blob_gas_used,
3269 excess_blob_gas,
3270 };
3271
3272 return match (block_access_list, slot_number) {
3274 (Some(block_access_list), Some(slot_number)) => {
3275 Ok(ExecutionPayload::V4(ExecutionPayloadV4 {
3276 payload_inner: v3,
3277 block_access_list,
3278 slot_number,
3279 }))
3280 }
3281 (None, None) => Ok(ExecutionPayload::V3(v3)),
3283 _ => Err(serde::de::Error::custom("invalid enum variant")),
3284 };
3285 }
3286
3287 if blob_gas_used.is_some() || excess_blob_gas.is_some() {
3289 return Err(serde::de::Error::custom("invalid enum variant"));
3290 }
3291
3292 if block_access_list.is_some() || slot_number.is_some() {
3294 return Err(serde::de::Error::custom("invalid enum variant"));
3295 }
3296
3297 Ok(ExecutionPayload::V2(ExecutionPayloadV2 { payload_inner: v1, withdrawals }))
3298 }
3299 }
3300
3301 const FIELDS: &[&str] = &[
3302 "parentHash",
3303 "feeRecipient",
3304 "stateRoot",
3305 "receiptsRoot",
3306 "logsBloom",
3307 "prevRandao",
3308 "blockNumber",
3309 "gasLimit",
3310 "gasUsed",
3311 "timestamp",
3312 "extraData",
3313 "baseFeePerGas",
3314 "blockHash",
3315 "transactions",
3316 "withdrawals",
3317 "blobGasUsed",
3318 "excessBlobGas",
3319 "blockAccessList",
3320 "slotNumber",
3321 ];
3322 deserializer.deserialize_struct("ExecutionPayload", FIELDS, ExecutionPayloadVisitor)
3323 }
3324}
3325
3326#[derive(Clone, Debug, PartialEq, Eq)]
3330#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3331#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
3332#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
3333pub struct ExecutionPayloadBodyV1 {
3334 pub transactions: Vec<Bytes>,
3336 pub withdrawals: Option<Vec<Withdrawal>>,
3340}
3341
3342impl ExecutionPayloadBodyV1 {
3343 pub fn new<'a, T>(
3345 withdrawals: Option<Withdrawals>,
3346 transactions: impl IntoIterator<Item = &'a T>,
3347 ) -> Self
3348 where
3349 T: Encodable2718 + 'a,
3350 {
3351 Self {
3352 transactions: transactions.into_iter().map(|tx| tx.encoded_2718().into()).collect(),
3353 withdrawals: withdrawals.map(Withdrawals::into_inner),
3354 }
3355 }
3356
3357 pub fn from_block<T: Encodable2718, H>(block: Block<T, H>) -> Self {
3359 let BlockBody { withdrawals, transactions, .. } = block.into_body();
3360 Self::new(withdrawals, transactions.iter())
3361 }
3362}
3363
3364impl<T: Encodable2718, H> From<Block<T, H>> for ExecutionPayloadBodyV1 {
3365 fn from(value: Block<T, H>) -> Self {
3366 Self::from_block(value)
3367 }
3368}
3369
3370#[derive(Clone, Debug, PartialEq, Eq)]
3376#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3377#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
3378#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
3379#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
3380pub struct ExecutionPayloadBodyV2 {
3381 pub transactions: Vec<Bytes>,
3383 pub withdrawals: Option<Vec<Withdrawal>>,
3387 pub block_access_list: Option<Bytes>,
3391}
3392
3393impl ExecutionPayloadBodyV2 {
3394 pub fn new<'a, T>(
3397 withdrawals: Option<Withdrawals>,
3398 transactions: impl IntoIterator<Item = &'a T>,
3399 block_access_list: Option<Bytes>,
3400 ) -> Self
3401 where
3402 T: Encodable2718 + 'a,
3403 {
3404 Self {
3405 transactions: transactions.into_iter().map(|tx| tx.encoded_2718().into()).collect(),
3406 withdrawals: withdrawals.map(Withdrawals::into_inner),
3407 block_access_list,
3408 }
3409 }
3410
3411 pub fn from_block<T: Encodable2718, H>(
3414 block: Block<T, H>,
3415 block_access_list: Option<Bytes>,
3416 ) -> Self {
3417 let BlockBody { withdrawals, transactions, .. } = block.into_body();
3418 Self::new(withdrawals, transactions.iter(), block_access_list)
3419 }
3420}
3421
3422impl From<ExecutionPayloadBodyV1> for ExecutionPayloadBodyV2 {
3423 fn from(v1: ExecutionPayloadBodyV1) -> Self {
3424 Self { transactions: v1.transactions, withdrawals: v1.withdrawals, block_access_list: None }
3425 }
3426}
3427
3428impl From<ExecutionPayloadBodyV2> for ExecutionPayloadBodyV1 {
3429 fn from(v2: ExecutionPayloadBodyV2) -> Self {
3430 Self { transactions: v2.transactions, withdrawals: v2.withdrawals }
3431 }
3432}
3433
3434#[derive(Clone, Debug, Default, PartialEq, Eq)]
3437#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3438#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
3439#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
3440pub struct PayloadAttributes {
3441 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
3443 pub timestamp: u64,
3444 pub prev_randao: B256,
3446 pub suggested_fee_recipient: Address,
3448 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
3451 pub withdrawals: Option<Vec<Withdrawal>>,
3452 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
3456 pub parent_beacon_block_root: Option<B256>,
3457 #[cfg_attr(
3461 feature = "serde",
3462 serde(
3463 default,
3464 skip_serializing_if = "Option::is_none",
3465 with = "alloy_serde::quantity::opt"
3466 )
3467 )]
3468 pub slot_number: Option<u64>,
3469 #[cfg_attr(
3473 feature = "serde",
3474 serde(
3475 default,
3476 skip_serializing_if = "Option::is_none",
3477 with = "alloy_serde::quantity::opt"
3478 )
3479 )]
3480 pub target_gas_limit: Option<u64>,
3481}
3482
3483impl PayloadAttributes {
3484 pub const fn with_timestamp(mut self, timestamp: u64) -> Self {
3486 self.timestamp = timestamp;
3487 self
3488 }
3489
3490 pub fn with_withdrawals(mut self, withdrawals: Vec<Withdrawal>) -> Self {
3492 self.withdrawals = Some(withdrawals);
3493 self
3494 }
3495
3496 pub const fn with_parent_beacon_block_root(mut self, parent_beacon_block_root: B256) -> Self {
3498 self.parent_beacon_block_root = Some(parent_beacon_block_root);
3499 self
3500 }
3501
3502 pub const fn with_slot_number(mut self, slot_number: u64) -> Self {
3504 self.slot_number = Some(slot_number);
3505 self
3506 }
3507}
3508
3509#[cfg(feature = "ssz")]
3510impl PayloadAttributes {
3511 fn ssz_v1_fixed_len() -> usize {
3512 <u64 as ssz::Encode>::ssz_fixed_len()
3513 + <B256 as ssz::Encode>::ssz_fixed_len()
3514 + <Address as ssz::Encode>::ssz_fixed_len()
3515 }
3516
3517 fn ssz_v2_fixed_len() -> usize {
3518 Self::ssz_v1_fixed_len() + <Vec<Withdrawal> as ssz::Encode>::ssz_fixed_len()
3519 }
3520
3521 fn ssz_v3_fixed_len() -> usize {
3522 Self::ssz_v2_fixed_len() + <B256 as ssz::Encode>::ssz_fixed_len()
3523 }
3524
3525 fn ssz_v4_slot_fixed_len() -> usize {
3526 Self::ssz_v3_fixed_len() + <u64 as ssz::Encode>::ssz_fixed_len()
3527 }
3528
3529 fn ssz_v4_target_fixed_len() -> usize {
3530 Self::ssz_v4_slot_fixed_len() + <u64 as ssz::Encode>::ssz_fixed_len()
3531 }
3532
3533 fn ssz_fixed_section_len(&self) -> usize {
3534 if self.target_gas_limit.is_some() {
3535 Self::ssz_v4_target_fixed_len()
3536 } else if self.slot_number.is_some() {
3537 Self::ssz_v4_slot_fixed_len()
3538 } else if self.parent_beacon_block_root.is_some() {
3539 Self::ssz_v3_fixed_len()
3540 } else if self.withdrawals.is_some() {
3541 Self::ssz_v2_fixed_len()
3542 } else {
3543 Self::ssz_v1_fixed_len()
3544 }
3545 }
3546}
3547
3548#[cfg(feature = "ssz")]
3549impl ssz::Encode for PayloadAttributes {
3550 fn is_ssz_fixed_len() -> bool {
3551 false
3552 }
3553
3554 fn ssz_append(&self, buf: &mut Vec<u8>) {
3555 let fixed_section_len = self.ssz_fixed_section_len();
3556 let mut encoder = ssz::SszEncoder::container(buf, fixed_section_len);
3557
3558 encoder.append(&self.timestamp);
3559 encoder.append(&self.prev_randao);
3560 encoder.append(&self.suggested_fee_recipient);
3561
3562 if fixed_section_len >= Self::ssz_v2_fixed_len() {
3563 let empty_withdrawals = Vec::new();
3564 let withdrawals = self.withdrawals.as_ref().unwrap_or(&empty_withdrawals);
3565 encoder.append(withdrawals);
3566 }
3567
3568 if fixed_section_len >= Self::ssz_v3_fixed_len() {
3569 encoder.append(&self.parent_beacon_block_root.unwrap_or_default());
3570 }
3571
3572 if fixed_section_len >= Self::ssz_v4_slot_fixed_len() {
3573 encoder.append(&self.slot_number.unwrap_or_default());
3574 }
3575
3576 if fixed_section_len == Self::ssz_v4_target_fixed_len() {
3577 encoder.append(&self.target_gas_limit.unwrap_or_default());
3578 }
3579
3580 encoder.finalize();
3581 }
3582
3583 fn ssz_bytes_len(&self) -> usize {
3584 let fixed_section_len = self.ssz_fixed_section_len();
3585 let withdrawals_len = if fixed_section_len >= Self::ssz_v2_fixed_len() {
3586 self.withdrawals.as_ref().map(ssz::Encode::ssz_bytes_len).unwrap_or_default()
3587 } else {
3588 0
3589 };
3590
3591 fixed_section_len + withdrawals_len
3592 }
3593}
3594
3595#[cfg(feature = "ssz")]
3596impl ssz::Decode for PayloadAttributes {
3597 fn is_ssz_fixed_len() -> bool {
3598 false
3599 }
3600
3601 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
3602 if bytes.len() == Self::ssz_v1_fixed_len() {
3603 let mut builder = ssz::SszDecoderBuilder::new(bytes);
3604
3605 builder.register_type::<u64>()?;
3606 builder.register_type::<B256>()?;
3607 builder.register_type::<Address>()?;
3608
3609 let mut decoder = builder.build()?;
3610
3611 return Ok(Self {
3612 timestamp: decoder.decode_next()?,
3613 prev_randao: decoder.decode_next()?,
3614 suggested_fee_recipient: decoder.decode_next()?,
3615 withdrawals: None,
3616 parent_beacon_block_root: None,
3617 slot_number: None,
3618 target_gas_limit: None,
3619 });
3620 }
3621
3622 if bytes.len() < Self::ssz_v2_fixed_len() {
3623 return Err(ssz::DecodeError::InvalidByteLength {
3624 len: bytes.len(),
3625 expected: Self::ssz_v2_fixed_len(),
3626 });
3627 }
3628
3629 let offset = u32::from_le_bytes([
3630 bytes[Self::ssz_v1_fixed_len()],
3631 bytes[Self::ssz_v1_fixed_len() + 1],
3632 bytes[Self::ssz_v1_fixed_len() + 2],
3633 bytes[Self::ssz_v1_fixed_len() + 3],
3634 ]) as usize;
3635
3636 let mut builder = ssz::SszDecoderBuilder::new(bytes);
3637
3638 builder.register_type::<u64>()?;
3639 builder.register_type::<B256>()?;
3640 builder.register_type::<Address>()?;
3641 builder.register_type::<Vec<Withdrawal>>()?;
3642
3643 match offset {
3644 offset if offset == Self::ssz_v2_fixed_len() => {
3645 let mut decoder = builder.build()?;
3646
3647 Ok(Self {
3648 timestamp: decoder.decode_next()?,
3649 prev_randao: decoder.decode_next()?,
3650 suggested_fee_recipient: decoder.decode_next()?,
3651 withdrawals: Some(decoder.decode_next()?),
3652 parent_beacon_block_root: None,
3653 slot_number: None,
3654 target_gas_limit: None,
3655 })
3656 }
3657 offset if offset == Self::ssz_v3_fixed_len() => {
3658 builder.register_type::<B256>()?;
3659 let mut decoder = builder.build()?;
3660
3661 Ok(Self {
3662 timestamp: decoder.decode_next()?,
3663 prev_randao: decoder.decode_next()?,
3664 suggested_fee_recipient: decoder.decode_next()?,
3665 withdrawals: Some(decoder.decode_next()?),
3666 parent_beacon_block_root: Some(decoder.decode_next()?),
3667 slot_number: None,
3668 target_gas_limit: None,
3669 })
3670 }
3671 offset if offset == Self::ssz_v4_slot_fixed_len() => {
3672 builder.register_type::<B256>()?;
3673 builder.register_type::<u64>()?;
3674 let mut decoder = builder.build()?;
3675
3676 Ok(Self {
3677 timestamp: decoder.decode_next()?,
3678 prev_randao: decoder.decode_next()?,
3679 suggested_fee_recipient: decoder.decode_next()?,
3680 withdrawals: Some(decoder.decode_next()?),
3681 parent_beacon_block_root: Some(decoder.decode_next()?),
3682 slot_number: Some(decoder.decode_next()?),
3683 target_gas_limit: None,
3684 })
3685 }
3686 offset if offset == Self::ssz_v4_target_fixed_len() => {
3687 builder.register_type::<B256>()?;
3688 builder.register_type::<u64>()?;
3689 builder.register_type::<u64>()?;
3690 let mut decoder = builder.build()?;
3691
3692 Ok(Self {
3693 timestamp: decoder.decode_next()?,
3694 prev_randao: decoder.decode_next()?,
3695 suggested_fee_recipient: decoder.decode_next()?,
3696 withdrawals: Some(decoder.decode_next()?),
3697 parent_beacon_block_root: Some(decoder.decode_next()?),
3698 slot_number: Some(decoder.decode_next()?),
3699 target_gas_limit: Some(decoder.decode_next()?),
3700 })
3701 }
3702 offset => Err(ssz::DecodeError::BytesInvalid(format!(
3703 "invalid PayloadAttributes SSZ fixed section offset: {offset}"
3704 ))),
3705 }
3706 }
3707}
3708
3709#[derive(Clone, Debug, PartialEq, Eq)]
3711#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
3712#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
3713#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
3714pub struct PayloadStatus {
3715 #[cfg_attr(feature = "serde", serde(flatten))]
3717 pub status: PayloadStatusEnum,
3718 pub latest_valid_hash: Option<B256>,
3720}
3721
3722impl PayloadStatus {
3723 pub const fn new(status: PayloadStatusEnum, latest_valid_hash: Option<B256>) -> Self {
3725 Self { status, latest_valid_hash }
3726 }
3727
3728 pub const fn from_status(status: PayloadStatusEnum) -> Self {
3730 Self { status, latest_valid_hash: None }
3731 }
3732
3733 pub const fn with_latest_valid_hash(mut self, latest_valid_hash: B256) -> Self {
3735 self.latest_valid_hash = Some(latest_valid_hash);
3736 self
3737 }
3738
3739 pub const fn maybe_latest_valid_hash(mut self, latest_valid_hash: Option<B256>) -> Self {
3741 self.latest_valid_hash = latest_valid_hash;
3742 self
3743 }
3744
3745 pub const fn is_syncing(&self) -> bool {
3747 self.status.is_syncing()
3748 }
3749
3750 pub const fn is_valid(&self) -> bool {
3752 self.status.is_valid()
3753 }
3754
3755 pub const fn is_invalid(&self) -> bool {
3757 self.status.is_invalid()
3758 }
3759}
3760
3761#[cfg(feature = "ssz")]
3762impl ssz::Encode for PayloadStatus {
3763 fn is_ssz_fixed_len() -> bool {
3764 false
3765 }
3766
3767 fn ssz_append(&self, buf: &mut Vec<u8>) {
3768 let validation_error =
3769 self.status.validation_error().map(str::as_bytes).unwrap_or_default().to_vec();
3770 let latest_valid_hash = self.latest_valid_hash.unwrap_or_default();
3771 let offset = <u8 as ssz::Encode>::ssz_fixed_len()
3772 + <B256 as ssz::Encode>::ssz_fixed_len()
3773 + <Vec<u8> as ssz::Encode>::ssz_fixed_len();
3774 let mut encoder = ssz::SszEncoder::container(buf, offset);
3775
3776 encoder.append(&self.status.ssz_code());
3777 encoder.append(&latest_valid_hash);
3778 encoder.append(&validation_error);
3779
3780 encoder.finalize();
3781 }
3782
3783 fn ssz_bytes_len(&self) -> usize {
3784 let validation_error_len = self.status.validation_error().map(str::len).unwrap_or_default();
3785 <u8 as ssz::Encode>::ssz_fixed_len()
3786 + <B256 as ssz::Encode>::ssz_fixed_len()
3787 + <Vec<u8> as ssz::Encode>::ssz_fixed_len()
3788 + validation_error_len
3789 }
3790}
3791
3792#[cfg(feature = "ssz")]
3793impl ssz::Decode for PayloadStatus {
3794 fn is_ssz_fixed_len() -> bool {
3795 false
3796 }
3797
3798 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
3799 let mut builder = ssz::SszDecoderBuilder::new(bytes);
3800
3801 builder.register_type::<u8>()?;
3802 builder.register_type::<B256>()?;
3803 builder.register_type::<Vec<u8>>()?;
3804
3805 let mut decoder = builder.build()?;
3806 let status_code: u8 = decoder.decode_next()?;
3807 let latest_valid_hash: B256 = decoder.decode_next()?;
3808 let validation_error: Vec<u8> = decoder.decode_next()?;
3809
3810 let status = PayloadStatusEnum::from_ssz_code(status_code, validation_error)?;
3811 let latest_valid_hash = (!latest_valid_hash.is_zero()).then_some(latest_valid_hash);
3812
3813 Ok(Self { status, latest_valid_hash })
3814 }
3815}
3816
3817impl core::fmt::Display for PayloadStatus {
3818 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3819 write!(
3820 f,
3821 "PayloadStatus {{ status: {}, latestValidHash: {:?} }}",
3822 self.status, self.latest_valid_hash
3823 )
3824 }
3825}
3826
3827#[cfg(feature = "serde")]
3828impl serde::Serialize for PayloadStatus {
3829 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3830 where
3831 S: serde::Serializer,
3832 {
3833 use serde::ser::SerializeMap;
3834 let mut map = serializer.serialize_map(Some(3))?;
3835 map.serialize_entry("status", self.status.as_str())?;
3836 map.serialize_entry("latestValidHash", &self.latest_valid_hash)?;
3837 map.serialize_entry("validationError", &self.status.validation_error())?;
3838 map.end()
3839 }
3840}
3841
3842impl From<PayloadError> for PayloadStatusEnum {
3843 fn from(error: PayloadError) -> Self {
3844 Self::Invalid { validation_error: error.to_string() }
3845 }
3846}
3847
3848#[derive(Clone, Debug, PartialEq, Eq)]
3850#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3851#[cfg_attr(feature = "serde", serde(tag = "status", rename_all = "SCREAMING_SNAKE_CASE"))]
3852#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
3853pub enum PayloadStatusEnum {
3854 Valid,
3858
3859 Invalid {
3863 #[cfg_attr(feature = "serde", serde(rename = "validationError"))]
3865 validation_error: String,
3866 },
3867
3868 Syncing,
3872
3873 Accepted,
3876}
3877
3878impl PayloadStatusEnum {
3879 pub const fn as_str(&self) -> &'static str {
3881 match self {
3882 Self::Valid => "VALID",
3883 Self::Invalid { .. } => "INVALID",
3884 Self::Syncing => "SYNCING",
3885 Self::Accepted => "ACCEPTED",
3886 }
3887 }
3888
3889 pub fn validation_error(&self) -> Option<&str> {
3891 match self {
3892 Self::Invalid { validation_error } => Some(validation_error),
3893 _ => None,
3894 }
3895 }
3896
3897 pub const fn is_syncing(&self) -> bool {
3899 matches!(self, Self::Syncing)
3900 }
3901
3902 pub const fn is_valid(&self) -> bool {
3904 matches!(self, Self::Valid)
3905 }
3906
3907 pub const fn is_invalid(&self) -> bool {
3909 matches!(self, Self::Invalid { .. })
3910 }
3911
3912 #[cfg(feature = "ssz")]
3913 const fn ssz_code(&self) -> u8 {
3914 match self {
3915 Self::Valid => 0,
3916 Self::Invalid { .. } => 1,
3917 Self::Syncing => 2,
3918 Self::Accepted => 3,
3919 }
3920 }
3921
3922 #[cfg(feature = "ssz")]
3923 fn from_ssz_code(status_code: u8, validation_error: Vec<u8>) -> Result<Self, ssz::DecodeError> {
3924 match status_code {
3925 0 => {
3926 if !validation_error.is_empty() {
3927 return Err(ssz::DecodeError::BytesInvalid(
3928 "unexpected validation error for VALID status".to_string(),
3929 ));
3930 }
3931 Ok(Self::Valid)
3932 }
3933 1 => String::from_utf8(validation_error)
3934 .map(|validation_error| Self::Invalid { validation_error })
3935 .map_err(|err| ssz::DecodeError::BytesInvalid(err.to_string())),
3936 2 => {
3937 if !validation_error.is_empty() {
3938 return Err(ssz::DecodeError::BytesInvalid(
3939 "unexpected validation error for SYNCING status".to_string(),
3940 ));
3941 }
3942 Ok(Self::Syncing)
3943 }
3944 3 => {
3945 if !validation_error.is_empty() {
3946 return Err(ssz::DecodeError::BytesInvalid(
3947 "unexpected validation error for ACCEPTED status".to_string(),
3948 ));
3949 }
3950 Ok(Self::Accepted)
3951 }
3952 _ => Err(ssz::DecodeError::BytesInvalid("unknown payload status code".to_string())),
3953 }
3954 }
3955}
3956
3957impl core::fmt::Display for PayloadStatusEnum {
3958 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3959 match self {
3960 Self::Invalid { validation_error } => {
3961 f.write_str(self.as_str())?;
3962 f.write_str(": ")?;
3963 f.write_str(validation_error.as_str())
3964 }
3965 _ => f.write_str(self.as_str()),
3966 }
3967 }
3968}
3969
3970#[derive(Clone, Debug, PartialEq, Eq)]
3976#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
3977#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
3978#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
3979pub struct PayloadStatusV2 {
3980 #[cfg_attr(feature = "serde", serde(flatten))]
3982 pub payload_inner: PayloadStatus,
3983 #[cfg_attr(feature = "serde", serde(default))]
3985 pub inclusion_list_satisfied: Option<bool>,
3986}
3987
3988impl PayloadStatusV2 {
3989 pub const fn new(
3991 payload_status: PayloadStatus,
3992 inclusion_list_satisfied: Option<bool>,
3993 ) -> Self {
3994 Self { payload_inner: payload_status, inclusion_list_satisfied }
3995 }
3996
3997 pub const fn with_inclusion_list_satisfied(mut self, satisfied: bool) -> Self {
3999 self.inclusion_list_satisfied = Some(satisfied);
4000 self
4001 }
4002
4003 pub const fn is_syncing(&self) -> bool {
4005 self.payload_inner.is_syncing()
4006 }
4007
4008 pub const fn is_valid(&self) -> bool {
4010 self.payload_inner.is_valid()
4011 }
4012
4013 pub const fn is_invalid(&self) -> bool {
4015 self.payload_inner.is_invalid()
4016 }
4017}
4018
4019impl From<PayloadStatus> for PayloadStatusV2 {
4020 fn from(payload_status: PayloadStatus) -> Self {
4021 Self::new(payload_status, None)
4022 }
4023}
4024
4025impl From<PayloadStatusV2> for PayloadStatus {
4027 fn from(payload_status: PayloadStatusV2) -> Self {
4028 payload_status.payload_inner
4029 }
4030}
4031
4032#[cfg(feature = "serde")]
4033impl serde::Serialize for PayloadStatusV2 {
4034 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
4035 where
4036 S: serde::Serializer,
4037 {
4038 use serde::ser::SerializeMap;
4039
4040 let mut map = serializer.serialize_map(Some(4))?;
4041 map.serialize_entry("status", self.payload_inner.status.as_str())?;
4042 map.serialize_entry("latestValidHash", &self.payload_inner.latest_valid_hash)?;
4043 map.serialize_entry("validationError", &self.payload_inner.status.validation_error())?;
4044 map.serialize_entry("inclusionListSatisfied", &self.inclusion_list_satisfied)?;
4045 map.end()
4046 }
4047}
4048
4049#[derive(Debug, Clone)]
4052#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
4053#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
4054pub struct ExecutionData {
4055 pub payload: ExecutionPayload,
4057 pub sidecar: ExecutionPayloadSidecar,
4059}
4060
4061impl ExecutionData {
4062 pub const fn new(payload: ExecutionPayload, sidecar: ExecutionPayloadSidecar) -> Self {
4064 Self { payload, sidecar }
4065 }
4066
4067 pub fn from_block_unchecked<T, H>(block_hash: B256, block: &Block<T, H>) -> Self
4080 where
4081 T: Encodable2718 + Transaction,
4082 H: BlockHeader,
4083 {
4084 let (payload, sidecar) = ExecutionPayload::from_block_unchecked(block_hash, block);
4085 Self::new(payload, sidecar)
4086 }
4087
4088 pub const fn parent_hash(&self) -> B256 {
4090 self.payload.parent_hash()
4091 }
4092
4093 pub const fn block_hash(&self) -> B256 {
4095 self.payload.block_hash()
4096 }
4097
4098 pub const fn block_number(&self) -> u64 {
4100 self.payload.block_number()
4101 }
4102
4103 pub fn parent_beacon_block_root(&self) -> Option<B256> {
4105 self.sidecar.parent_beacon_block_root()
4106 }
4107
4108 pub const fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
4110 self.payload.withdrawals()
4111 }
4112
4113 pub const fn transaction_count(&self) -> usize {
4115 self.payload.transactions().len()
4116 }
4117
4118 pub fn try_into_block<T: Decodable2718>(
4129 self,
4130 ) -> Result<alloy_consensus::Block<T>, PayloadError> {
4131 self.try_into_block_with(|tx| {
4132 T::decode_2718_exact(tx.as_ref())
4133 .map_err(alloy_rlp::Error::from)
4134 .map_err(PayloadError::from)
4135 })
4136 }
4137
4138 pub fn try_into_block_with<T, F, E>(
4149 self,
4150 f: F,
4151 ) -> Result<alloy_consensus::Block<T>, PayloadError>
4152 where
4153 F: FnMut(Bytes) -> Result<T, E>,
4154 E: Into<PayloadError>,
4155 {
4156 self.payload.try_into_block_with_sidecar_with(&self.sidecar, f)
4157 }
4158
4159 pub fn into_block_raw(self) -> Result<Block<Bytes>, PayloadError> {
4164 let mut base_block = self.payload.into_block_raw()?;
4165 base_block.header.parent_beacon_block_root = self.sidecar.parent_beacon_block_root();
4166 base_block.header.requests_hash = self.sidecar.requests_hash();
4167 Ok(base_block)
4168 }
4169}
4170
4171impl<T, H> From<Sealed<Block<T, H>>> for ExecutionData
4172where
4173 T: Encodable2718 + Transaction,
4174 H: BlockHeader,
4175{
4176 fn from(sealed: Sealed<Block<T, H>>) -> Self {
4177 let (block, block_hash) = sealed.into_parts();
4178 Self::from_block_unchecked(block_hash, &block)
4179 }
4180}
4181
4182impl<T, H> From<Sealed<&Block<T, H>>> for ExecutionData
4183where
4184 T: Encodable2718 + Transaction,
4185 H: BlockHeader,
4186{
4187 fn from(sealed: Sealed<&Block<T, H>>) -> Self {
4188 let (block, block_hash) = sealed.into_parts();
4189 Self::from_block_unchecked(block_hash, block)
4190 }
4191}
4192
4193impl<T, H> From<(Sealed<Block<T, H>>, PayloadExtras)> for ExecutionData
4194where
4195 T: Encodable2718 + Transaction,
4196 H: BlockHeader,
4197{
4198 fn from((sealed, extras): (Sealed<Block<T, H>>, PayloadExtras)) -> Self {
4199 let (block, block_hash) = sealed.into_parts();
4200 let (payload, sidecar) =
4201 ExecutionPayload::from_block_unchecked_with_extras(block_hash, &block, extras);
4202 Self::new(payload, sidecar)
4203 }
4204}
4205
4206impl<T, H> From<(Sealed<&Block<T, H>>, PayloadExtras)> for ExecutionData
4207where
4208 T: Encodable2718 + Transaction,
4209 H: BlockHeader,
4210{
4211 fn from((sealed, extras): (Sealed<&Block<T, H>>, PayloadExtras)) -> Self {
4212 let (block, block_hash) = sealed.into_parts();
4213 let (payload, sidecar) =
4214 ExecutionPayload::from_block_unchecked_with_extras(block_hash, block, extras);
4215 Self::new(payload, sidecar)
4216 }
4217}
4218
4219#[cfg(test)]
4220mod tests {
4221 use super::*;
4222 use crate::{CancunPayloadFields, PayloadValidationError};
4223 use alloc::vec;
4224 use alloy_consensus::TxEnvelope;
4225 use alloy_primitives::{b256, hex};
4226 use similar_asserts::assert_eq;
4227
4228 #[test]
4229 #[cfg(feature = "kzg")]
4230 fn convert_empty_bundle() {
4231 let bundle = BlobsBundleV1::default();
4232 let _sidecar = bundle.try_into_sidecar().unwrap();
4233 }
4234
4235 #[test]
4236 #[cfg(feature = "serde")]
4237 fn serde_blobsbundlev1_empty() {
4238 let blobs_bundle_v1 = BlobsBundleV1::empty();
4239
4240 let serialized = serde_json::to_string(&blobs_bundle_v1).unwrap();
4241 let deserialized: BlobsBundleV1 = serde_json::from_str(&serialized).unwrap();
4242 assert_eq!(deserialized, blobs_bundle_v1);
4243 }
4244
4245 #[test]
4246 #[cfg(feature = "serde")]
4247 fn serde_blobsbundlev1_not_empty_pass() {
4248 let blobs_bundle_v1 = BlobsBundleV1 {
4249 proofs: vec![Bytes48::default()],
4250 commitments: vec![Bytes48::default()],
4251 blobs: vec![Blob::default()],
4252 };
4253
4254 let serialized = serde_json::to_string(&blobs_bundle_v1).unwrap();
4255 let deserialized = std::thread::Builder::new()
4257 .stack_size(1024 * 1024)
4258 .spawn(move || serde_json::from_str::<BlobsBundleV1>(&serialized).unwrap())
4259 .unwrap()
4260 .join()
4261 .unwrap();
4262 assert_eq!(deserialized, blobs_bundle_v1);
4263 }
4264
4265 #[test]
4266 #[cfg(feature = "serde")]
4267 fn serde_blobsbundlev1_not_empty_fail() {
4268 let blobs_bundle_v1 = BlobsBundleV1 {
4269 proofs: vec![Bytes48::default(), Bytes48::default()],
4270 commitments: vec![Bytes48::default()],
4271 blobs: vec![Blob::default()],
4272 };
4273
4274 let serialized = serde_json::to_string(&blobs_bundle_v1).unwrap();
4275 let deserialized: Result<BlobsBundleV1, serde_json::Error> =
4276 serde_json::from_str(&serialized);
4277 assert!(deserialized.is_err(), "invalid length 2, expected commitments.len()");
4278 }
4279
4280 #[test]
4281 #[cfg(feature = "serde")]
4282 fn serde_blobsbundlev2_not_empty_pass() {
4283 let commitments = vec![Bytes48::default()];
4284
4285 let blobs_bundle_v2 = BlobsBundleV2 {
4286 proofs: vec![Bytes48::default(); commitments.len() * CELLS_PER_EXT_BLOB],
4287 commitments,
4288 blobs: vec![Blob::default()],
4289 };
4290
4291 let serialized = serde_json::to_string(&blobs_bundle_v2).unwrap();
4292 let deserialized = std::thread::Builder::new()
4294 .stack_size(1024 * 1024)
4295 .spawn(move || serde_json::from_str::<BlobsBundleV2>(&serialized).unwrap())
4296 .unwrap()
4297 .join()
4298 .unwrap();
4299 assert_eq!(deserialized, blobs_bundle_v2);
4300 }
4301
4302 #[test]
4303 #[cfg(feature = "serde")]
4304 fn serde_blobsbundlev2_not_empty_fail() {
4305 let blobs_bundle_v2 = BlobsBundleV2 {
4306 proofs: vec![Bytes48::default()],
4307 commitments: vec![Bytes48::default()],
4308 blobs: vec![],
4309 };
4310
4311 let serialized = serde_json::to_string(&blobs_bundle_v2).unwrap();
4312 let deserialized: Result<BlobsBundleV2, serde_json::Error> =
4313 serde_json::from_str(&serialized);
4314 assert!(deserialized.is_err());
4315 }
4316
4317 #[test]
4318 #[cfg(feature = "ssz")]
4319 #[cfg(not(debug_assertions))]
4320 fn ssz_blobsbundlev2_roundtrip() {
4321 let commitments = vec![Bytes48::default(), Bytes48::default()];
4322 let num_blobs = commitments.len();
4323
4324 let blobs_bundle_v2 = BlobsBundleV2 {
4325 commitments,
4326 proofs: vec![Bytes48::default(); num_blobs * CELLS_PER_EXT_BLOB],
4327 blobs: vec![Blob::default(); num_blobs],
4328 };
4329
4330 let encoded = ssz::Encode::as_ssz_bytes(&blobs_bundle_v2);
4331 let decoded: BlobsBundleV2 = ssz::Decode::from_ssz_bytes(&encoded).unwrap();
4332
4333 assert_eq!(decoded, blobs_bundle_v2);
4334 }
4335
4336 #[test]
4337 #[cfg(feature = "ssz")]
4338 #[cfg(not(debug_assertions))]
4339 fn ssz_blobsbundlev2_invalid_proofs_length() {
4340 let commitments = vec![Bytes48::default()];
4341
4342 let blobs_bundle_v2 = BlobsBundleV2 {
4343 commitments,
4344 proofs: vec![Bytes48::default(); 2],
4345 blobs: vec![Blob::default()],
4346 };
4347
4348 let encoded = ssz::Encode::as_ssz_bytes(&blobs_bundle_v2);
4349
4350 let result: Result<BlobsBundleV2, _> = ssz::Decode::from_ssz_bytes(&encoded);
4352 assert!(result.is_err());
4353 }
4354
4355 #[test]
4356 #[cfg(feature = "ssz")]
4357 #[cfg(not(debug_assertions))]
4358 fn ssz_blobsbundlev2_mismatched_commitments_blobs() {
4359 let blobs_bundle_v2 = BlobsBundleV2 {
4360 commitments: vec![Bytes48::default(), Bytes48::default()],
4361 proofs: vec![Bytes48::default(); CELLS_PER_EXT_BLOB],
4362 blobs: vec![Blob::default()],
4363 };
4364
4365 let encoded = ssz::Encode::as_ssz_bytes(&blobs_bundle_v2);
4366
4367 let result: Result<BlobsBundleV2, _> = ssz::Decode::from_ssz_bytes(&encoded);
4369 assert!(result.is_err());
4370 }
4371
4372 #[test]
4373 #[cfg(feature = "ssz")]
4374 fn ssz_blobsbundlev2_empty() {
4375 let blobs_bundle_v2 = BlobsBundleV2 { commitments: vec![], proofs: vec![], blobs: vec![] };
4376
4377 let encoded = ssz::Encode::as_ssz_bytes(&blobs_bundle_v2);
4378
4379 let decoded: BlobsBundleV2 = ssz::Decode::from_ssz_bytes(&encoded).unwrap();
4381 assert_eq!(decoded, blobs_bundle_v2);
4382 }
4383
4384 #[cfg(feature = "ssz")]
4385 fn ssz_payload_v1() -> ExecutionPayloadV1 {
4386 ExecutionPayloadV1 {
4387 parent_hash: B256::with_last_byte(1),
4388 fee_recipient: Address::with_last_byte(2),
4389 state_root: B256::with_last_byte(3),
4390 receipts_root: B256::with_last_byte(4),
4391 logs_bloom: Bloom::default(),
4392 prev_randao: B256::with_last_byte(5),
4393 block_number: 6,
4394 gas_limit: 7,
4395 gas_used: 8,
4396 timestamp: 9,
4397 extra_data: Bytes::from(vec![10, 11]),
4398 base_fee_per_gas: U256::from(12),
4399 block_hash: B256::with_last_byte(13),
4400 transactions: vec![Bytes::from(vec![14, 15])],
4401 }
4402 }
4403
4404 #[cfg(feature = "ssz")]
4405 fn ssz_payload_v2() -> ExecutionPayloadV2 {
4406 ExecutionPayloadV2 {
4407 payload_inner: ssz_payload_v1(),
4408 withdrawals: vec![Withdrawal {
4409 index: 1,
4410 validator_index: 2,
4411 address: Address::with_last_byte(3),
4412 amount: 4,
4413 }],
4414 }
4415 }
4416
4417 #[cfg(feature = "ssz")]
4418 fn ssz_payload_v3() -> ExecutionPayloadV3 {
4419 ExecutionPayloadV3 {
4420 payload_inner: ssz_payload_v2(),
4421 blob_gas_used: 16,
4422 excess_blob_gas: 17,
4423 }
4424 }
4425
4426 #[cfg(feature = "ssz")]
4427 fn ssz_payload_v4() -> ExecutionPayloadV4 {
4428 ExecutionPayloadV4 {
4429 payload_inner: ssz_payload_v3(),
4430 block_access_list: Bytes::from(vec![18, 19]),
4431 slot_number: 20,
4432 }
4433 }
4434
4435 #[test]
4436 #[cfg(feature = "ssz")]
4437 fn ssz_execution_payload_envelope_v1_response_roundtrip() {
4438 use ssz::{Decode, Encode};
4439
4440 let payload = ssz_payload_v1();
4441 let decoded = ExecutionPayloadV1::from_ssz_bytes(&payload.as_ssz_bytes()).unwrap();
4442
4443 assert_eq!(decoded, payload);
4444 }
4445
4446 #[test]
4447 #[cfg(feature = "ssz")]
4448 fn ssz_execution_payload_envelope_v2_roundtrip() {
4449 use ssz::{Decode, Encode};
4450
4451 let envelope = ExecutionPayloadEnvelopeV2 {
4452 execution_payload: ExecutionPayloadFieldV2::V2(ssz_payload_v2()),
4453 block_value: U256::from(21),
4454 };
4455
4456 let decoded = ExecutionPayloadEnvelopeV2::from_ssz_bytes(&envelope.as_ssz_bytes()).unwrap();
4457 assert_eq!(decoded, envelope);
4458
4459 let envelope = ExecutionPayloadEnvelopeV2 {
4460 execution_payload: ExecutionPayloadFieldV2::V1(ssz_payload_v1()),
4461 block_value: U256::from(22),
4462 };
4463
4464 let decoded = ExecutionPayloadEnvelopeV2::from_ssz_bytes(&envelope.as_ssz_bytes()).unwrap();
4465 assert_eq!(decoded, envelope);
4466 }
4467
4468 #[test]
4469 #[cfg(feature = "ssz")]
4470 fn ssz_execution_payload_envelope_v3_roundtrip() {
4471 use ssz::{Decode, Encode};
4472
4473 let envelope = ExecutionPayloadEnvelopeV3 {
4474 execution_payload: ssz_payload_v3(),
4475 block_value: U256::from(23),
4476 blobs_bundle: BlobsBundleV1::empty(),
4477 should_override_builder: true,
4478 };
4479
4480 let decoded = ExecutionPayloadEnvelopeV3::from_ssz_bytes(&envelope.as_ssz_bytes()).unwrap();
4481 assert_eq!(decoded, envelope);
4482 }
4483
4484 #[test]
4485 #[cfg(feature = "ssz")]
4486 fn ssz_execution_payload_envelope_v4_roundtrip() {
4487 use ssz::{Decode, Encode};
4488
4489 let envelope = ExecutionPayloadEnvelopeV4 {
4490 envelope_inner: ExecutionPayloadEnvelopeV3 {
4491 execution_payload: ssz_payload_v3(),
4492 block_value: U256::from(24),
4493 blobs_bundle: BlobsBundleV1::empty(),
4494 should_override_builder: false,
4495 },
4496 execution_requests: Requests::from_requests([Bytes::from(vec![1, 2, 3])]),
4497 };
4498
4499 let decoded = ExecutionPayloadEnvelopeV4::from_ssz_bytes(&envelope.as_ssz_bytes()).unwrap();
4500 assert_eq!(decoded, envelope);
4501 }
4502
4503 #[test]
4504 #[cfg(feature = "ssz")]
4505 fn ssz_execution_payload_envelope_v5_roundtrip() {
4506 use ssz::{Decode, Encode};
4507
4508 let envelope = ExecutionPayloadEnvelopeV5 {
4509 execution_payload: ssz_payload_v3(),
4510 block_value: U256::from(25),
4511 blobs_bundle: BlobsBundleV2::empty(),
4512 should_override_builder: true,
4513 execution_requests: Requests::from_requests([Bytes::from(vec![4, 5, 6])]),
4514 };
4515
4516 let decoded = ExecutionPayloadEnvelopeV5::from_ssz_bytes(&envelope.as_ssz_bytes()).unwrap();
4517 assert_eq!(decoded, envelope);
4518 }
4519
4520 #[test]
4521 #[cfg(feature = "ssz")]
4522 fn ssz_execution_payload_envelope_v6_roundtrip() {
4523 use ssz::{Decode, Encode};
4524
4525 let envelope = ExecutionPayloadEnvelopeV6 {
4526 execution_payload: ssz_payload_v4(),
4527 block_value: U256::from(26),
4528 blobs_bundle: BlobsBundleV2::empty(),
4529 should_override_builder: false,
4530 execution_requests: Requests::from_requests([Bytes::from(vec![7, 8, 9])]),
4531 };
4532
4533 let decoded = ExecutionPayloadEnvelopeV6::from_ssz_bytes(&envelope.as_ssz_bytes()).unwrap();
4534 assert_eq!(decoded, envelope);
4535 }
4536
4537 #[test]
4538 #[cfg(feature = "serde")]
4539 fn serde_payload_status() {
4540 let s = r#"{"status":"SYNCING","latestValidHash":null,"validationError":null}"#;
4541 let status: PayloadStatus = serde_json::from_str(s).unwrap();
4542 assert_eq!(status.status, PayloadStatusEnum::Syncing);
4543 assert!(status.latest_valid_hash.is_none());
4544 assert!(status.status.validation_error().is_none());
4545 assert_eq!(serde_json::to_string(&status).unwrap(), s);
4546
4547 let full = s;
4548 let s = r#"{"status":"SYNCING","latestValidHash":null}"#;
4549 let status: PayloadStatus = serde_json::from_str(s).unwrap();
4550 assert_eq!(status.status, PayloadStatusEnum::Syncing);
4551 assert!(status.latest_valid_hash.is_none());
4552 assert!(status.status.validation_error().is_none());
4553 assert_eq!(serde_json::to_string(&status).unwrap(), full);
4554 }
4555
4556 #[test]
4557 #[cfg(feature = "serde")]
4558 fn serde_payload_status_v2() {
4559 let json = r#"{"status":"VALID","latestValidHash":null,"validationError":null,"inclusionListSatisfied":true}"#;
4560 let status: PayloadStatusV2 = serde_json::from_str(json).unwrap();
4561 assert!(status.is_valid());
4562 assert!(status.payload_inner.latest_valid_hash.is_none());
4563 assert_eq!(status.inclusion_list_satisfied, Some(true));
4564 assert_eq!(serde_json::to_string(&status).unwrap(), json);
4565
4566 let json = r#"{"status":"SYNCING","latestValidHash":null,"validationError":null,"inclusionListSatisfied":null}"#;
4567 let status: PayloadStatusV2 = serde_json::from_str(json).unwrap();
4568 assert!(status.is_syncing());
4569 assert_eq!(status.inclusion_list_satisfied, None);
4570 assert_eq!(serde_json::to_string(&status).unwrap(), json);
4571 }
4572
4573 #[test]
4574 fn payload_status_v2_conversions() {
4575 let v1 = PayloadStatus::from_status(PayloadStatusEnum::Valid)
4576 .with_latest_valid_hash(B256::with_last_byte(1));
4577
4578 let v2: PayloadStatusV2 = v1.clone().into();
4579 assert_eq!(v2.payload_inner, v1);
4580 assert_eq!(v2.inclusion_list_satisfied, None);
4581
4582 let downgraded: PayloadStatus = v2.with_inclusion_list_satisfied(true).into();
4583 assert_eq!(downgraded, v1);
4584 }
4585
4586 #[test]
4587 fn payload_attributes_builder_setters() {
4588 let withdrawal = Withdrawal {
4589 index: 1,
4590 validator_index: 2,
4591 address: Address::with_last_byte(3),
4592 amount: 4,
4593 };
4594 let parent_beacon_block_root = B256::with_last_byte(5);
4595
4596 let attributes = PayloadAttributes::default()
4597 .with_timestamp(10)
4598 .with_withdrawals(vec![withdrawal])
4599 .with_parent_beacon_block_root(parent_beacon_block_root)
4600 .with_slot_number(6);
4601
4602 assert_eq!(attributes.timestamp, 10);
4603 assert_eq!(attributes.withdrawals, Some(vec![withdrawal]));
4604 assert_eq!(attributes.parent_beacon_block_root, Some(parent_beacon_block_root));
4605 assert_eq!(attributes.slot_number, Some(6));
4606 }
4607
4608 #[test]
4609 #[cfg(feature = "ssz")]
4610 fn ssz_payload_attributes_roundtrip_all_versions() {
4611 use ssz::{Decode, Encode};
4612
4613 let withdrawal = Withdrawal {
4614 index: 1,
4615 validator_index: 2,
4616 address: Address::with_last_byte(3),
4617 amount: 4,
4618 };
4619
4620 let v1 = PayloadAttributes {
4621 timestamp: 10,
4622 prev_randao: B256::with_last_byte(11),
4623 suggested_fee_recipient: Address::with_last_byte(12),
4624 withdrawals: None,
4625 parent_beacon_block_root: None,
4626 slot_number: None,
4627 target_gas_limit: None,
4628 };
4629 let decoded_v1 = PayloadAttributes::from_ssz_bytes(&v1.as_ssz_bytes()).unwrap();
4630 assert_eq!(decoded_v1, v1);
4631
4632 let v2 = PayloadAttributes {
4633 timestamp: 20,
4634 prev_randao: B256::with_last_byte(21),
4635 suggested_fee_recipient: Address::with_last_byte(22),
4636 withdrawals: Some(vec![withdrawal]),
4637 parent_beacon_block_root: None,
4638 slot_number: None,
4639 target_gas_limit: None,
4640 };
4641 let decoded_v2 = PayloadAttributes::from_ssz_bytes(&v2.as_ssz_bytes()).unwrap();
4642 assert_eq!(decoded_v2, v2);
4643
4644 let v3 = PayloadAttributes {
4645 timestamp: 30,
4646 prev_randao: B256::with_last_byte(31),
4647 suggested_fee_recipient: Address::with_last_byte(32),
4648 withdrawals: Some(vec![withdrawal]),
4649 parent_beacon_block_root: Some(B256::with_last_byte(33)),
4650 slot_number: None,
4651 target_gas_limit: None,
4652 };
4653 let decoded_v3 = PayloadAttributes::from_ssz_bytes(&v3.as_ssz_bytes()).unwrap();
4654 assert_eq!(decoded_v3, v3);
4655
4656 let v4 = PayloadAttributes {
4657 timestamp: 40,
4658 prev_randao: B256::with_last_byte(41),
4659 suggested_fee_recipient: Address::with_last_byte(42),
4660 withdrawals: Some(vec![withdrawal]),
4661 parent_beacon_block_root: Some(B256::with_last_byte(43)),
4662 slot_number: Some(44),
4663 target_gas_limit: Some(45),
4664 };
4665 let decoded_v4 = PayloadAttributes::from_ssz_bytes(&v4.as_ssz_bytes()).unwrap();
4666 assert_eq!(decoded_v4, v4);
4667 }
4668
4669 #[test]
4670 #[cfg(feature = "ssz")]
4671 fn ssz_payload_attributes_match_spec_container_offsets() {
4672 use ssz::Encode;
4673
4674 let withdrawal = Withdrawal {
4675 index: 1,
4676 validator_index: 2,
4677 address: Address::with_last_byte(3),
4678 amount: 4,
4679 };
4680
4681 let v1 = PayloadAttributes {
4682 timestamp: 10,
4683 prev_randao: B256::with_last_byte(11),
4684 suggested_fee_recipient: Address::with_last_byte(12),
4685 withdrawals: None,
4686 parent_beacon_block_root: None,
4687 slot_number: None,
4688 target_gas_limit: None,
4689 };
4690 assert_eq!(v1.as_ssz_bytes().len(), 60);
4691
4692 let v2 = PayloadAttributes {
4693 timestamp: 20,
4694 prev_randao: B256::with_last_byte(21),
4695 suggested_fee_recipient: Address::with_last_byte(22),
4696 withdrawals: Some(vec![withdrawal]),
4697 parent_beacon_block_root: None,
4698 slot_number: None,
4699 target_gas_limit: None,
4700 };
4701 let bytes = v2.as_ssz_bytes();
4702 assert_eq!(u32::from_le_bytes(bytes[60..64].try_into().unwrap()), 64);
4703
4704 let v3 = PayloadAttributes {
4705 timestamp: 30,
4706 prev_randao: B256::with_last_byte(31),
4707 suggested_fee_recipient: Address::with_last_byte(32),
4708 withdrawals: Some(vec![withdrawal]),
4709 parent_beacon_block_root: Some(B256::with_last_byte(33)),
4710 slot_number: None,
4711 target_gas_limit: None,
4712 };
4713 let bytes = v3.as_ssz_bytes();
4714 assert_eq!(u32::from_le_bytes(bytes[60..64].try_into().unwrap()), 96);
4715
4716 let v4 = PayloadAttributes {
4717 timestamp: 40,
4718 prev_randao: B256::with_last_byte(41),
4719 suggested_fee_recipient: Address::with_last_byte(42),
4720 withdrawals: Some(vec![withdrawal]),
4721 parent_beacon_block_root: Some(B256::with_last_byte(43)),
4722 slot_number: Some(44),
4723 target_gas_limit: Some(45),
4724 };
4725 let bytes = v4.as_ssz_bytes();
4726 assert_eq!(u32::from_le_bytes(bytes[60..64].try_into().unwrap()), 112);
4727 }
4728
4729 #[test]
4730 #[cfg(feature = "ssz")]
4731 fn ssz_payload_status_roundtrip() {
4732 use ssz::{Decode, Encode};
4733
4734 let statuses = [
4735 PayloadStatus {
4736 status: PayloadStatusEnum::Valid,
4737 latest_valid_hash: Some(B256::with_last_byte(1)),
4738 },
4739 PayloadStatus {
4740 status: PayloadStatusEnum::Invalid { validation_error: "bad payload".to_string() },
4741 latest_valid_hash: Some(B256::with_last_byte(2)),
4742 },
4743 PayloadStatus { status: PayloadStatusEnum::Syncing, latest_valid_hash: None },
4744 PayloadStatus { status: PayloadStatusEnum::Accepted, latest_valid_hash: None },
4745 ];
4746
4747 for status in statuses {
4748 let decoded = PayloadStatus::from_ssz_bytes(&status.as_ssz_bytes()).unwrap();
4749 assert_eq!(decoded, status);
4750 }
4751 }
4752
4753 #[test]
4754 #[cfg(feature = "ssz")]
4755 fn ssz_payload_status_matches_eip8178_container() {
4756 use ssz::{Decode, Encode};
4757
4758 let status = PayloadStatus {
4759 status: PayloadStatusEnum::Invalid { validation_error: "bad payload".to_string() },
4760 latest_valid_hash: None,
4761 };
4762 let spec = (1u8, B256::ZERO, b"bad payload".to_vec());
4763
4764 assert_eq!(status.as_ssz_bytes(), spec.as_ssz_bytes());
4765 assert_eq!(PayloadStatus::from_ssz_bytes(&spec.as_ssz_bytes()).unwrap(), status);
4766 }
4767
4768 #[test]
4769 #[cfg(feature = "ssz")]
4770 fn ssz_payload_id_roundtrip() {
4771 use ssz::{Decode, Encode};
4772
4773 let payload_id = PayloadId(B64::with_last_byte(42));
4774 let decoded = PayloadId::from_ssz_bytes(&payload_id.as_ssz_bytes()).unwrap();
4775 assert_eq!(decoded, payload_id);
4776 }
4777
4778 #[test]
4779 fn payload_id_from_str() {
4780 let expected = PayloadId(B64::with_last_byte(42));
4781
4782 assert_eq!("0x000000000000002a".parse::<PayloadId>().unwrap(), expected);
4783 assert_eq!("000000000000002a".parse::<PayloadId>().unwrap(), expected);
4784 }
4785
4786 #[test]
4787 fn payload_id_from_str_rejects_invalid_hex() {
4788 assert!("0x2a".parse::<PayloadId>().is_err());
4789 assert!("0x00000000000000zz".parse::<PayloadId>().is_err());
4790 }
4791
4792 #[test]
4793 #[cfg(feature = "serde")]
4794 fn serde_payload_status_error_deserialize() {
4795 let s = r#"{"status":"INVALID","latestValidHash":null,"validationError":"Failed to decode block"}"#;
4796 let q = PayloadStatus {
4797 latest_valid_hash: None,
4798 status: PayloadStatusEnum::Invalid {
4799 validation_error: "Failed to decode block".to_string(),
4800 },
4801 };
4802 assert_eq!(q, serde_json::from_str(s).unwrap());
4803
4804 let s = r#"{"status":"INVALID","latestValidHash":null,"validationError":"links to previously rejected block"}"#;
4805 let q = PayloadStatus {
4806 latest_valid_hash: None,
4807 status: PayloadStatusEnum::Invalid {
4808 validation_error: PayloadValidationError::LinksToRejectedPayload.to_string(),
4809 },
4810 };
4811 assert_eq!(q, serde_json::from_str(s).unwrap());
4812
4813 let s = r#"{"status":"INVALID","latestValidHash":null,"validationError":"invalid block number"}"#;
4814 let q = PayloadStatus {
4815 latest_valid_hash: None,
4816 status: PayloadStatusEnum::Invalid {
4817 validation_error: PayloadValidationError::InvalidBlockNumber.to_string(),
4818 },
4819 };
4820 assert_eq!(q, serde_json::from_str(s).unwrap());
4821
4822 let s = r#"{"status":"INVALID","latestValidHash":null,"validationError":
4823 "invalid merkle root: (remote: 0x3f77fb29ce67436532fee970e1add8f5cc80e8878c79b967af53b1fd92a0cab7 local: 0x603b9628dabdaadb442a3bb3d7e0360efc110e1948472909230909f1690fed17)"}"#;
4824 let q = PayloadStatus {
4825 latest_valid_hash: None,
4826 status: PayloadStatusEnum::Invalid {
4827 validation_error: PayloadValidationError::InvalidStateRoot {
4828 remote: "0x3f77fb29ce67436532fee970e1add8f5cc80e8878c79b967af53b1fd92a0cab7"
4829 .parse()
4830 .unwrap(),
4831 local: "0x603b9628dabdaadb442a3bb3d7e0360efc110e1948472909230909f1690fed17"
4832 .parse()
4833 .unwrap(),
4834 }
4835 .to_string(),
4836 },
4837 };
4838 assert_eq!(q, serde_json::from_str(s).unwrap());
4839 }
4840
4841 #[test]
4842 #[cfg(feature = "serde")]
4843 fn serde_roundtrip_legacy_txs_payload_v1() {
4844 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"]}"#;
4846 let payload: ExecutionPayloadV1 = serde_json::from_str(s).unwrap();
4847 assert_eq!(serde_json::to_string(&payload).unwrap(), s);
4848
4849 let any_payload: ExecutionPayload = serde_json::from_str(s).unwrap();
4850 assert_eq!(any_payload, payload.into());
4851 }
4852
4853 #[test]
4854 #[cfg(feature = "serde")]
4855 fn serde_roundtrip_legacy_txs_payload_v3() {
4856 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"}"#;
4858 let payload: ExecutionPayloadV3 = serde_json::from_str(s).unwrap();
4859 assert_eq!(serde_json::to_string(&payload).unwrap(), s);
4860
4861 let any_payload: ExecutionPayload = serde_json::from_str(s).unwrap();
4862 assert_eq!(any_payload, payload.into());
4863 }
4864
4865 #[test]
4866 #[cfg(feature = "serde")]
4867 fn serde_roundtrip_enveloped_txs_payload_v1() {
4868 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"]}"#;
4870 let payload: ExecutionPayloadV1 = serde_json::from_str(s).unwrap();
4871 assert_eq!(serde_json::to_string(&payload).unwrap(), s);
4872
4873 let any_payload: ExecutionPayload = serde_json::from_str(s).unwrap();
4874 assert_eq!(any_payload, payload.into());
4875 }
4876
4877 #[test]
4878 #[cfg(feature = "serde")]
4879 fn serde_roundtrip_enveloped_txs_payload_v3() {
4880 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"}"#;
4882 let payload: ExecutionPayloadV3 = serde_json::from_str(s).unwrap();
4883 assert_eq!(serde_json::to_string(&payload).unwrap(), s);
4884
4885 let any_payload: ExecutionPayload = serde_json::from_str(s).unwrap();
4886 assert_eq!(any_payload, payload.into());
4887 }
4888
4889 #[test]
4890 #[cfg(feature = "serde")]
4891 fn serde_roundtrip_execution_payload_envelope_v3() {
4892 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}"#;
4894 let envelope: ExecutionPayloadEnvelopeV3 = serde_json::from_str(response).unwrap();
4895 assert_eq!(serde_json::to_string(&envelope).unwrap(), response);
4896 }
4897
4898 #[test]
4899 #[cfg(feature = "serde")]
4900 fn serde_roundtrip_execution_payload_field_v2() {
4901 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"}]}"#;
4903 let field: ExecutionPayloadFieldV2 = serde_json::from_str(s).unwrap();
4904 let payload_v2: ExecutionPayloadV2 = serde_json::from_str(s).unwrap();
4905 assert_eq!(field, ExecutionPayloadFieldV2::V2(payload_v2));
4906 assert_eq!(serde_json::to_string(&field).unwrap(), s);
4907
4908 let s_empty = s.replace(
4910 r#"[{"index":"0x0","validatorIndex":"0x1","address":"0x00000000000000000000000000000000000010f0","amount":"0x64"}]"#,
4911 "[]",
4912 );
4913 let field: ExecutionPayloadFieldV2 = serde_json::from_str(&s_empty).unwrap();
4914 let payload_v2: ExecutionPayloadV2 = serde_json::from_str(&s_empty).unwrap();
4915 assert_eq!(field, ExecutionPayloadFieldV2::V2(payload_v2));
4916 assert_eq!(serde_json::to_string(&field).unwrap(), s_empty);
4917
4918 let s_v1 = s_empty.replace(r#","withdrawals":[]"#, "");
4920 let field: ExecutionPayloadFieldV2 = serde_json::from_str(&s_v1).unwrap();
4921 let payload_v1: ExecutionPayloadV1 = serde_json::from_str(&s_v1).unwrap();
4922 assert_eq!(field, ExecutionPayloadFieldV2::V1(payload_v1));
4923 assert_eq!(serde_json::to_string(&field).unwrap(), s_v1);
4924 }
4925
4926 #[test]
4927 #[cfg(feature = "serde")]
4928 fn serde_roundtrip_execution_payload_envelope_v2() {
4929 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"}"#;
4931 let envelope: ExecutionPayloadEnvelopeV2 = serde_json::from_str(response).unwrap();
4932 assert!(matches!(envelope.execution_payload, ExecutionPayloadFieldV2::V2(_)));
4933 assert_eq!(serde_json::to_string(&envelope).unwrap(), response);
4934 }
4935
4936 #[test]
4937 #[cfg(feature = "serde")]
4938 fn serde_payload_input_enum_v3() {
4939 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"}"#;
4940
4941 let payload: ExecutionPayload = serde_json::from_str(response_v3).unwrap();
4942 assert!(payload.as_v3().is_some());
4943 assert_eq!(serde_json::to_string(&payload).unwrap(), response_v3);
4944
4945 let payload_v3: ExecutionPayloadV3 = serde_json::from_str(response_v3).unwrap();
4946 assert_eq!(payload.as_v3().unwrap(), &payload_v3);
4947 }
4948
4949 #[test]
4950 #[cfg(feature = "serde")]
4951 fn serde_payload_input_enum_v2() {
4952 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":[]}"#;
4953
4954 let payload: ExecutionPayload = serde_json::from_str(response_v2).unwrap();
4955 assert!(payload.as_v3().is_none());
4956 assert!(payload.as_v2().is_some());
4957 assert_eq!(serde_json::to_string(&payload).unwrap(), response_v2);
4958
4959 let payload_v2: ExecutionPayloadV2 = serde_json::from_str(response_v2).unwrap();
4960 assert_eq!(payload.as_v2().unwrap(), &payload_v2);
4961 }
4962
4963 #[test]
4964 #[cfg(feature = "serde")]
4965 fn serde_payload_input_enum_faulty_v2() {
4966 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"}"#;
4968
4969 let payload: Result<ExecutionPayload, serde_json::Error> =
4970 serde_json::from_str(response_faulty);
4971 assert!(payload.is_err());
4972 }
4973
4974 #[test]
4975 #[cfg(feature = "serde")]
4976 fn serde_payload_input_enum_faulty_v1() {
4977 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"}"#;
4979
4980 let payload: Result<ExecutionPayload, serde_json::Error> =
4981 serde_json::from_str(response_faulty);
4982 assert!(payload.is_err());
4983 }
4984
4985 #[test]
4986 #[cfg(feature = "serde")]
4987 fn serde_faulty_roundtrip_payload_input_v3() {
4988 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"}"#;
4992
4993 let payload_v2: ExecutionPayloadV2 = serde_json::from_str(response_v3).unwrap();
4994 assert_ne!(response_v3, serde_json::to_string(&payload_v2).unwrap());
4995
4996 let payload_v1: ExecutionPayloadV1 = serde_json::from_str(response_v3).unwrap();
4997 assert_ne!(response_v3, serde_json::to_string(&payload_v1).unwrap());
4998 }
4999
5000 #[test]
5001 #[cfg(feature = "serde")]
5002 fn serde_faulty_roundtrip_payload_input_v2() {
5003 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":[]}"#;
5007
5008 let payload: ExecutionPayloadV1 = serde_json::from_str(response_v2).unwrap();
5009 assert_ne!(response_v2, serde_json::to_string(&payload).unwrap());
5010 }
5011
5012 #[test]
5013 #[cfg(feature = "serde")]
5014 fn serde_deserialize_execution_payload_input_v2() {
5015 let response = r#"
5016{
5017 "baseFeePerGas": "0x173b30b3",
5018 "blockHash": "0x99d486755fd046ad0bbb60457bac93d4856aa42fa00629cc7e4a28b65b5f8164",
5019 "blockNumber": "0xb",
5020 "extraData": "0xd883010d01846765746888676f312e32302e33856c696e7578",
5021 "feeRecipient": "0x0000000000000000000000000000000000000000",
5022 "gasLimit": "0x405829",
5023 "gasUsed": "0x3f0ca0",
5024 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
5025 "parentHash": "0xfe34aaa2b869c66a727783ee5ad3e3983b6ef22baf24a1e502add94e7bcac67a",
5026 "prevRandao": "0x74132c32fe3ab9a470a8352544514d21b6969e7749f97742b53c18a1b22b396c",
5027 "receiptsRoot": "0x6a5c41dc55a1bd3e74e7f6accc799efb08b00c36c15265058433fcea6323e95f",
5028 "stateRoot": "0xde3b357f5f099e4c33d0343c9e9d204d663d7bd9c65020a38e5d0b2a9ace78a2",
5029 "timestamp": "0x6507d6b4",
5030 "transactions": [
5031 "0xf86d0a8458b20efd825208946177843db3138ae69679a54b95cf345ed759450d8806f3e8d87878800080820a95a0f8bddb1dcc4558b532ff747760a6f547dd275afdbe7bdecc90680e71de105757a014f34ba38c180913c0543b0ac2eccfb77cc3f801a535008dc50e533fbe435f53",
5032 "0xf86d0b8458b20efd82520894687704db07e902e9a8b3754031d168d46e3d586e8806f3e8d87878800080820a95a0e3108f710902be662d5c978af16109961ffaf2ac4f88522407d40949a9574276a0205719ed21889b42ab5c1026d40b759a507c12d92db0d100fa69e1ac79137caa",
5033 "0xf86d0c8458b20efd8252089415e6a5a2e131dd5467fa1ff3acd104f45ee5940b8806f3e8d87878800080820a96a0af556ba9cda1d686239e08c24e169dece7afa7b85e0948eaa8d457c0561277fca029da03d3af0978322e54ac7e8e654da23934e0dd839804cb0430f8aaafd732dc",
5034 "0xf8521784565adcb7830186a0808080820a96a0ec782872a673a9fe4eff028a5bdb30d6b8b7711f58a187bf55d3aec9757cb18ea001796d373da76f2b0aeda72183cce0ad070a4f03aa3e6fee4c757a9444245206",
5035 "0xf8521284565adcb7830186a0808080820a95a08a0ea89028eff02596b385a10e0bd6ae098f3b281be2c95a9feb1685065d7384a06239d48a72e4be767bd12f317dd54202f5623a33e71e25a87cb25dd781aa2fc8",
5036 "0xf8521384565adcb7830186a0808080820a95a0784dbd311a82f822184a46f1677a428cbe3a2b88a798fb8ad1370cdbc06429e8a07a7f6a0efd428e3d822d1de9a050b8a883938b632185c254944dd3e40180eb79"
5037 ],
5038 "withdrawals": []
5039}
5040 "#;
5041 let payload: ExecutionPayloadInputV2 = serde_json::from_str(response).unwrap();
5042 assert_eq!(payload.withdrawals, Some(vec![]));
5043
5044 let response = r#"
5045{
5046 "baseFeePerGas": "0x173b30b3",
5047 "blockHash": "0x99d486755fd046ad0bbb60457bac93d4856aa42fa00629cc7e4a28b65b5f8164",
5048 "blockNumber": "0xb",
5049 "extraData": "0xd883010d01846765746888676f312e32302e33856c696e7578",
5050 "feeRecipient": "0x0000000000000000000000000000000000000000",
5051 "gasLimit": "0x405829",
5052 "gasUsed": "0x3f0ca0",
5053 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
5054 "parentHash": "0xfe34aaa2b869c66a727783ee5ad3e3983b6ef22baf24a1e502add94e7bcac67a",
5055 "prevRandao": "0x74132c32fe3ab9a470a8352544514d21b6969e7749f97742b53c18a1b22b396c",
5056 "receiptsRoot": "0x6a5c41dc55a1bd3e74e7f6accc799efb08b00c36c15265058433fcea6323e95f",
5057 "stateRoot": "0xde3b357f5f099e4c33d0343c9e9d204d663d7bd9c65020a38e5d0b2a9ace78a2",
5058 "timestamp": "0x6507d6b4",
5059 "transactions": [
5060 "0xf86d0a8458b20efd825208946177843db3138ae69679a54b95cf345ed759450d8806f3e8d87878800080820a95a0f8bddb1dcc4558b532ff747760a6f547dd275afdbe7bdecc90680e71de105757a014f34ba38c180913c0543b0ac2eccfb77cc3f801a535008dc50e533fbe435f53",
5061 "0xf86d0b8458b20efd82520894687704db07e902e9a8b3754031d168d46e3d586e8806f3e8d87878800080820a95a0e3108f710902be662d5c978af16109961ffaf2ac4f88522407d40949a9574276a0205719ed21889b42ab5c1026d40b759a507c12d92db0d100fa69e1ac79137caa",
5062 "0xf86d0c8458b20efd8252089415e6a5a2e131dd5467fa1ff3acd104f45ee5940b8806f3e8d87878800080820a96a0af556ba9cda1d686239e08c24e169dece7afa7b85e0948eaa8d457c0561277fca029da03d3af0978322e54ac7e8e654da23934e0dd839804cb0430f8aaafd732dc",
5063 "0xf8521784565adcb7830186a0808080820a96a0ec782872a673a9fe4eff028a5bdb30d6b8b7711f58a187bf55d3aec9757cb18ea001796d373da76f2b0aeda72183cce0ad070a4f03aa3e6fee4c757a9444245206",
5064 "0xf8521284565adcb7830186a0808080820a95a08a0ea89028eff02596b385a10e0bd6ae098f3b281be2c95a9feb1685065d7384a06239d48a72e4be767bd12f317dd54202f5623a33e71e25a87cb25dd781aa2fc8",
5065 "0xf8521384565adcb7830186a0808080820a95a0784dbd311a82f822184a46f1677a428cbe3a2b88a798fb8ad1370cdbc06429e8a07a7f6a0efd428e3d822d1de9a050b8a883938b632185c254944dd3e40180eb79"
5066 ]
5067}
5068 "#;
5069 let payload: ExecutionPayloadInputV2 = serde_json::from_str(response).unwrap();
5070 assert_eq!(payload.withdrawals, None);
5071 }
5072
5073 #[test]
5074 #[cfg(feature = "serde")]
5075 fn serde_deserialize_v2_input_with_blob_fields() {
5076 let input = r#"
5077{
5078 "parentHash": "0xaaa4c5b574f37e1537c78931d1bca24a4d17d4f29f1ee97e1cd48b704909de1f",
5079 "feeRecipient": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
5080 "stateRoot": "0x308ee9c5c6fab5e3d08763a3b5fe0be8ada891fa5010a49a3390e018dd436810",
5081 "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
5082 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
5083 "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000",
5084 "blockNumber": "0xf",
5085 "gasLimit": "0x16345785d8a0000",
5086 "gasUsed": "0x0",
5087 "timestamp": "0x3a97",
5088 "extraData": "0x",
5089 "baseFeePerGas": "0x7",
5090 "blockHash": "0x38bb6ba645c7e6bd970f9c7d492fafe1e04d85349054cb48d16c9d2c3e3cd0bf",
5091 "transactions": [],
5092 "withdrawals": [],
5093 "excessBlobGas": "0x0",
5094 "blobGasUsed": "0x0"
5095}
5096 "#;
5097
5098 let payload_res: Result<ExecutionPayloadInputV2, serde_json::Error> =
5100 serde_json::from_str(input);
5101 assert!(payload_res.is_err());
5102 }
5103
5104 #[test]
5106 #[cfg(feature = "serde")]
5107 fn deserialize_op_base_payload() {
5108 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"]}"#;
5109 let _payload = serde_json::from_str::<ExecutionPayloadInputV2>(payload).unwrap();
5110 }
5111
5112 #[test]
5113 fn roundtrip_payload_to_block() {
5114 let first_transaction_raw = Bytes::from_static(&hex!("02f9017a8501a1f0ff438211cc85012a05f2008512a05f2000830249f094d5409474fd5a725eab2ac9a8b26ca6fb51af37ef80b901040cc7326300000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000001bdd2ed4b616c800000000000000000000000000001e9ee781dd4b97bdef92e5d1785f73a1f931daa20000000000000000000000007a40026a3b9a41754a95eec8c92c6b99886f440c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000009ae80eb647dd09968488fa1d7e412bf8558a0b7a0000000000000000000000000f9815537d361cb02befd9918c95c97d4d8a4a2bc001a0ba8f1928bb0efc3fcd01524a2039a9a2588fa567cd9a7cc18217e05c615e9d69a0544bfd11425ac7748e76b3795b57a5563e2b0eff47b5428744c62ff19ccfc305")[..]);
5115 let second_transaction_raw = Bytes::from_static(&hex!("03f901388501a1f0ff430c843b9aca00843b9aca0082520894e7249813d8ccf6fa95a2203f46a64166073d58878080c005f8c6a00195f6dff17753fc89b60eac6477026a805116962c9e412de8015c0484e661c1a001aae314061d4f5bbf158f15d9417a238f9589783f58762cd39d05966b3ba2fba0013f5be9b12e7da06f0dd11a7bdc4e0db8ef33832acc23b183bd0a2c1408a757a0019d9ac55ea1a615d92965e04d960cb3be7bff121a381424f1f22865bd582e09a001def04412e76df26fefe7b0ed5e10580918ae4f355b074c0cfe5d0259157869a0011c11a415db57e43db07aef0de9280b591d65ca0cce36c7002507f8191e5d4a80a0c89b59970b119187d97ad70539f1624bbede92648e2dc007890f9658a88756c5a06fb2e3d4ce2c438c0856c2de34948b7032b1aadc4642a9666228ea8cdc7786b7")[..]);
5116
5117 let new_payload = ExecutionPayloadV3 {
5118 payload_inner: ExecutionPayloadV2 {
5119 payload_inner: ExecutionPayloadV1 {
5120 base_fee_per_gas: U256::from(7u64),
5121 block_number: 0xa946u64,
5122 block_hash: hex!("a5ddd3f286f429458a39cafc13ffe89295a7efa8eb363cf89a1a4887dbcf272b").into(),
5123 logs_bloom: hex!("00200004000000000000000080000000000200000000000000000000000000000000200000000000000000000000000000000000800000000200000000000000000000000000000000000008000000200000000000000000000001000000000000000000000000000000800000000000000000000100000000000030000000000000000040000000000000000000000000000000000800080080404000000000000008000000000008200000000000200000000000000000000000000000000000000002000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000100000000000000000000").into(),
5124 extra_data: hex!("d883010d03846765746888676f312e32312e31856c696e7578").into(),
5125 gas_limit: 0x1c9c380,
5126 gas_used: 0x1f4a9,
5127 timestamp: 0x651f35b8,
5128 fee_recipient: hex!("f97e180c050e5ab072211ad2c213eb5aee4df134").into(),
5129 parent_hash: hex!("d829192799c73ef28a7332313b3c03af1f2d5da2c36f8ecfafe7a83a3bfb8d1e").into(),
5130 prev_randao: hex!("753888cc4adfbeb9e24e01c84233f9d204f4a9e1273f0e29b43c4c148b2b8b7e").into(),
5131 receipts_root: hex!("4cbc48e87389399a0ea0b382b1c46962c4b8e398014bf0cc610f9c672bee3155").into(),
5132 state_root: hex!("017d7fa2b5adb480f5e05b2c95cb4186e12062eed893fc8822798eed134329d1").into(),
5133 transactions: vec![first_transaction_raw, second_transaction_raw],
5134 },
5135 withdrawals: vec![],
5136 },
5137 blob_gas_used: 0xc0000,
5138 excess_blob_gas: 0x580000,
5139 };
5140
5141 let mut block: Block<TxEnvelope> = new_payload.clone().try_into_block().unwrap();
5142
5143 let parent_beacon_block_root =
5146 b256!("531cd53b8e68deef0ea65edfa3cda927a846c307b0907657af34bc3f313b5871");
5147 block.header.parent_beacon_block_root = Some(parent_beacon_block_root);
5148
5149 let converted_payload = ExecutionPayloadV3::from_block_unchecked(block.hash_slow(), &block);
5150
5151 assert_eq!(new_payload, converted_payload);
5153 }
5154
5155 #[test]
5156 fn payload_to_block_rejects_network_encoded_tx() {
5157 let first_transaction_raw = Bytes::from_static(&hex!("b9017e02f9017a8501a1f0ff438211cc85012a05f2008512a05f2000830249f094d5409474fd5a725eab2ac9a8b26ca6fb51af37ef80b901040cc7326300000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000001bdd2ed4b616c800000000000000000000000000001e9ee781dd4b97bdef92e5d1785f73a1f931daa20000000000000000000000007a40026a3b9a41754a95eec8c92c6b99886f440c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000009ae80eb647dd09968488fa1d7e412bf8558a0b7a0000000000000000000000000f9815537d361cb02befd9918c95c97d4d8a4a2bc001a0ba8f1928bb0efc3fcd01524a2039a9a2588fa567cd9a7cc18217e05c615e9d69a0544bfd11425ac7748e76b3795b57a5563e2b0eff47b5428744c62ff19ccfc305")[..]);
5158 let second_transaction_raw = Bytes::from_static(&hex!("b9013c03f901388501a1f0ff430c843b9aca00843b9aca0082520894e7249813d8ccf6fa95a2203f46a64166073d58878080c005f8c6a00195f6dff17753fc89b60eac6477026a805116962c9e412de8015c0484e661c1a001aae314061d4f5bbf158f15d9417a238f9589783f58762cd39d05966b3ba2fba0013f5be9b12e7da06f0dd11a7bdc4e0db8ef33832acc23b183bd0a2c1408a757a0019d9ac55ea1a615d92965e04d960cb3be7bff121a381424f1f22865bd582e09a001def04412e76df26fefe7b0ed5e10580918ae4f355b074c0cfe5d0259157869a0011c11a415db57e43db07aef0de9280b591d65ca0cce36c7002507f8191e5d4a80a0c89b59970b119187d97ad70539f1624bbede92648e2dc007890f9658a88756c5a06fb2e3d4ce2c438c0856c2de34948b7032b1aadc4642a9666228ea8cdc7786b7")[..]);
5159
5160 let new_payload = ExecutionPayloadV3 {
5161 payload_inner: ExecutionPayloadV2 {
5162 payload_inner: ExecutionPayloadV1 {
5163 base_fee_per_gas: U256::from(7u64),
5164 block_number: 0xa946u64,
5165 block_hash: hex!("a5ddd3f286f429458a39cafc13ffe89295a7efa8eb363cf89a1a4887dbcf272b").into(),
5166 logs_bloom: hex!("00200004000000000000000080000000000200000000000000000000000000000000200000000000000000000000000000000000800000000200000000000000000000000000000000000008000000200000000000000000000001000000000000000000000000000000800000000000000000000100000000000030000000000000000040000000000000000000000000000000000800080080404000000000000008000000000008200000000000200000000000000000000000000000000000000002000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000100000000000000000000").into(),
5167 extra_data: hex!("d883010d03846765746888676f312e32312e31856c696e7578").into(),
5168 gas_limit: 0x1c9c380,
5169 gas_used: 0x1f4a9,
5170 timestamp: 0x651f35b8,
5171 fee_recipient: hex!("f97e180c050e5ab072211ad2c213eb5aee4df134").into(),
5172 parent_hash: hex!("d829192799c73ef28a7332313b3c03af1f2d5da2c36f8ecfafe7a83a3bfb8d1e").into(),
5173 prev_randao: hex!("753888cc4adfbeb9e24e01c84233f9d204f4a9e1273f0e29b43c4c148b2b8b7e").into(),
5174 receipts_root: hex!("4cbc48e87389399a0ea0b382b1c46962c4b8e398014bf0cc610f9c672bee3155").into(),
5175 state_root: hex!("017d7fa2b5adb480f5e05b2c95cb4186e12062eed893fc8822798eed134329d1").into(),
5176 transactions: vec![first_transaction_raw, second_transaction_raw],
5177 },
5178 withdrawals: vec![],
5179 },
5180 blob_gas_used: 0xc0000,
5181 excess_blob_gas: 0x580000,
5182 };
5183
5184 let _block = new_payload
5185 .try_into_block::<TxEnvelope>()
5186 .expect_err("execution payload conversion requires typed txs without a rlp header");
5187 }
5188
5189 #[test]
5190 fn devnet_invalid_block_hash_repro() {
5191 let deser_block = r#"
5192 {
5193 "parentHash": "0xae8315ee86002e6269a17dd1e9516a6cf13223e9d4544d0c32daff826fb31acc",
5194 "feeRecipient": "0xf97e180c050e5ab072211ad2c213eb5aee4df134",
5195 "stateRoot": "0x03787f1579efbaa4a8234e72465eb4e29ef7e62f61242d6454661932e1a282a1",
5196 "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
5197 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
5198 "prevRandao": "0x918e86b497dc15de7d606457c36ca583e24d9b0a110a814de46e33d5bb824a66",
5199 "blockNumber": "0x6a784",
5200 "gasLimit": "0x1c9c380",
5201 "gasUsed": "0x0",
5202 "timestamp": "0x65bc1d60",
5203 "extraData": "0x9a726574682f76302e312e302d616c7068612e31362f6c696e7578",
5204 "baseFeePerGas": "0x8",
5205 "blobGasUsed": "0x0",
5206 "excessBlobGas": "0x0",
5207 "blockHash": "0x340c157eca9fd206b87c17f0ecbe8d411219de7188a0a240b635c88a96fe91c5",
5208 "transactions": [],
5209 "withdrawals": [
5210 {
5211 "index": "0x5ab202",
5212 "validatorIndex": "0xb1b",
5213 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5214 "amount": "0x19b3d"
5215 },
5216 {
5217 "index": "0x5ab203",
5218 "validatorIndex": "0xb1c",
5219 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5220 "amount": "0x15892"
5221 },
5222 {
5223 "index": "0x5ab204",
5224 "validatorIndex": "0xb1d",
5225 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5226 "amount": "0x19b3d"
5227 },
5228 {
5229 "index": "0x5ab205",
5230 "validatorIndex": "0xb1e",
5231 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5232 "amount": "0x19b3d"
5233 },
5234 {
5235 "index": "0x5ab206",
5236 "validatorIndex": "0xb1f",
5237 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5238 "amount": "0x19b3d"
5239 },
5240 {
5241 "index": "0x5ab207",
5242 "validatorIndex": "0xb20",
5243 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5244 "amount": "0x19b3d"
5245 },
5246 {
5247 "index": "0x5ab208",
5248 "validatorIndex": "0xb21",
5249 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5250 "amount": "0x15892"
5251 },
5252 {
5253 "index": "0x5ab209",
5254 "validatorIndex": "0xb22",
5255 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5256 "amount": "0x19b3d"
5257 },
5258 {
5259 "index": "0x5ab20a",
5260 "validatorIndex": "0xb23",
5261 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5262 "amount": "0x19b3d"
5263 },
5264 {
5265 "index": "0x5ab20b",
5266 "validatorIndex": "0xb24",
5267 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5268 "amount": "0x17db2"
5269 },
5270 {
5271 "index": "0x5ab20c",
5272 "validatorIndex": "0xb25",
5273 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5274 "amount": "0x19b3d"
5275 },
5276 {
5277 "index": "0x5ab20d",
5278 "validatorIndex": "0xb26",
5279 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5280 "amount": "0x19b3d"
5281 },
5282 {
5283 "index": "0x5ab20e",
5284 "validatorIndex": "0xa91",
5285 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5286 "amount": "0x15892"
5287 },
5288 {
5289 "index": "0x5ab20f",
5290 "validatorIndex": "0xa92",
5291 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5292 "amount": "0x1c05d"
5293 },
5294 {
5295 "index": "0x5ab210",
5296 "validatorIndex": "0xa93",
5297 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5298 "amount": "0x15892"
5299 },
5300 {
5301 "index": "0x5ab211",
5302 "validatorIndex": "0xa94",
5303 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
5304 "amount": "0x19b3d"
5305 }
5306 ]
5307 }
5308 "#;
5309
5310 let payload: ExecutionPayload =
5312 serde_json::from_str::<ExecutionPayloadV3>(deser_block).unwrap().into();
5313
5314 let block_hash_with_blob_fee_fields =
5318 b256!("a7cdd5f9e54147b53a15833a8c45dffccbaed534d7fdc23458f45102a4bf71b0");
5319
5320 let versioned_hashes = vec![];
5321 let parent_beacon_block_root =
5322 b256!("1162de8a0f4d20d86b9ad6e0a2575ab60f00a433dc70d9318c8abc9041fddf54");
5323
5324 let cancun_fields = CancunPayloadFields { parent_beacon_block_root, versioned_hashes };
5326
5327 let block = payload
5329 .try_into_block_with_sidecar::<TxEnvelope>(&ExecutionPayloadSidecar::v3(cancun_fields))
5330 .unwrap();
5331
5332 assert_eq!(block_hash_with_blob_fee_fields, block.header.hash_slow());
5334 }
5335
5336 #[test]
5337 fn test_payload_to_block_with_sidecar_raw() {
5338 use std::path::PathBuf;
5339
5340 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/payload");
5341 let dir = std::fs::read_dir(path).expect("Unable to read payload folder");
5342
5343 for entry in dir {
5344 let entry = entry.expect("Unable to read entry");
5345 let path = entry.path();
5346
5347 if path.extension().and_then(|s| s.to_str()) != Some("json") {
5348 continue;
5349 }
5350
5351 let contents = std::fs::read_to_string(&path).expect("Unable to read file");
5352 let value: serde_json::Value = serde_json::from_str(&contents)
5353 .unwrap_or_else(|e| panic!("Failed to parse JSON from {path:?}: {e}"));
5354
5355 let new_payload = &value["newPayload"];
5357 let payload_value = &new_payload["payload"];
5358 let sidecar_value = &new_payload["sidecar"];
5359
5360 let payload: ExecutionPayload = serde_json::from_value(payload_value.clone())
5361 .unwrap_or_else(|e| panic!("Failed to deserialize payload from {path:?}: {e}"));
5362
5363 let sidecar: ExecutionPayloadSidecar = serde_json::from_value(sidecar_value.clone())
5365 .unwrap_or_else(|e| panic!("Failed to deserialize sidecar from {path:?}: {e}"));
5366
5367 let block = payload.clone().into_block_with_sidecar_raw(&sidecar).unwrap_or_else(|e| {
5369 panic!("Failed to convert payload to block from {path:?}: {e}")
5370 });
5371
5372 if let Some(tx_count) = payload_value["transactions"].as_array().map(|a| a.len()) {
5374 assert_eq!(
5375 block.body.transactions.len(),
5376 tx_count,
5377 "Transaction count mismatch in {:?}",
5378 path
5379 );
5380 }
5381
5382 assert_eq!(
5384 block.header.parent_beacon_block_root,
5385 sidecar.parent_beacon_block_root(),
5386 "Parent beacon block root mismatch in {:?}",
5387 path
5388 );
5389 assert_eq!(
5390 block.header.requests_hash,
5391 sidecar.requests_hash(),
5392 "Requests hash mismatch in {:?}",
5393 path
5394 );
5395
5396 let expected_hash = payload_value["blockHash"]
5398 .as_str()
5399 .unwrap()
5400 .parse::<B256>()
5401 .unwrap_or_else(|e| panic!("Failed to parse block hash from {path:?}: {e}"));
5402 let actual_hash = block.header.hash_slow();
5403 assert_eq!(
5404 actual_hash, expected_hash,
5405 "Block hash mismatch in {:?}: expected {}, got {}",
5406 path, expected_hash, actual_hash
5407 );
5408
5409 let block =
5410 payload.try_into_block_with_sidecar::<TxEnvelope>(&sidecar).unwrap_or_else(|e| {
5411 panic!("Failed to convert payload to block from {path:?}: {e}")
5412 });
5413 let actual_hash = block.header.hash_slow();
5414 assert_eq!(
5415 actual_hash, expected_hash,
5416 "Block hash mismatch in {:?}: expected {}, got {}",
5417 path, expected_hash, actual_hash
5418 );
5419 }
5420 }
5421
5422 #[test]
5423 #[cfg(feature = "serde")]
5424 fn test_into_block_raw_with_transactions_root() {
5425 use std::path::PathBuf;
5426
5427 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/payload");
5428 let dir = std::fs::read_dir(path).expect("Unable to read payload folder");
5429
5430 for entry in dir {
5431 let entry = entry.expect("Unable to read entry");
5432 let path = entry.path();
5433
5434 if path.extension().and_then(|s| s.to_str()) != Some("json") {
5435 continue;
5436 }
5437
5438 let contents = std::fs::read_to_string(&path).expect("Unable to read file");
5439 let value: serde_json::Value = serde_json::from_str(&contents)
5440 .unwrap_or_else(|e| panic!("Failed to parse JSON from {path:?}: {e}"));
5441
5442 let new_payload = &value["newPayload"];
5443 let payload_value = &new_payload["payload"];
5444 let sidecar_value = &new_payload["sidecar"];
5445
5446 let payload: ExecutionPayload = serde_json::from_value(payload_value.clone())
5447 .unwrap_or_else(|e| panic!("Failed to deserialize payload from {path:?}: {e}"));
5448
5449 let sidecar: ExecutionPayloadSidecar = serde_json::from_value(sidecar_value.clone())
5450 .unwrap_or_else(|e| panic!("Failed to deserialize sidecar from {path:?}: {e}"));
5451
5452 let expected_hash = payload_value["blockHash"]
5453 .as_str()
5454 .unwrap()
5455 .parse::<B256>()
5456 .unwrap_or_else(|e| panic!("Failed to parse block hash from {path:?}: {e}"));
5457
5458 let block_normal =
5460 payload.clone().into_block_with_sidecar_raw(&sidecar).unwrap_or_else(|e| {
5461 panic!("Failed to convert payload to block from {path:?}: {e}")
5462 });
5463 let tx_root = block_normal.header.transactions_root;
5464
5465 let block_with_root =
5467 payload.clone().into_block_raw_with_transactions_root(tx_root).unwrap();
5468 assert_eq!(
5469 block_with_root.header.transactions_root, tx_root,
5470 "transactions_root mismatch in {path:?}"
5471 );
5472
5473 let block_opt_some =
5475 payload.clone().into_block_raw_with_transactions_root_opt(Some(tx_root)).unwrap();
5476 assert_eq!(
5477 block_opt_some.header.transactions_root, tx_root,
5478 "opt(Some) transactions_root mismatch in {path:?}"
5479 );
5480
5481 let block_opt_none =
5483 payload.clone().into_block_raw_with_transactions_root_opt(None).unwrap();
5484 assert_eq!(
5485 block_opt_none.header.transactions_root, tx_root,
5486 "opt(None) transactions_root mismatch in {path:?}"
5487 );
5488
5489 let block_sidecar_root = payload
5491 .into_block_with_sidecar_raw_with_transactions_root(&sidecar, tx_root)
5492 .unwrap();
5493 let actual_hash = block_sidecar_root.header.hash_slow();
5494 assert_eq!(
5495 actual_hash, expected_hash,
5496 "Block hash mismatch with pre-computed tx root in {path:?}"
5497 );
5498 }
5499 }
5500
5501 #[test]
5502 fn test_v1_with_transactions_root_override() {
5503 let transaction = Bytes::from_static(&hex!("f86d0a8458b20efd825208946177843db3138ae69679a54b95cf345ed759450d8806f3e8d87878800080820a95a0f8bddb1dcc4558b532ff747760a6f547dd275afdbe7bdecc90680e71de105757a014f34ba38c180913c0543b0ac2eccfb77cc3f801a535008dc50e533fbe435f53"));
5504
5505 let payload = ExecutionPayloadV1 {
5506 parent_hash: B256::default(),
5507 fee_recipient: Address::default(),
5508 state_root: B256::default(),
5509 receipts_root: B256::default(),
5510 logs_bloom: Bloom::default(),
5511 prev_randao: B256::default(),
5512 block_number: 0,
5513 gas_limit: 0,
5514 gas_used: 0,
5515 timestamp: 0,
5516 extra_data: Bytes::default(),
5517 base_fee_per_gas: U256::from(1),
5518 block_hash: B256::default(),
5519 transactions: vec![transaction],
5520 };
5521
5522 let computed_root = payload.clone().into_block_raw().unwrap().header.transactions_root;
5523
5524 let fake_root = b256!("1111111111111111111111111111111111111111111111111111111111111111");
5525 assert_ne!(computed_root, fake_root);
5526
5527 let block = payload.clone().into_block_raw_with_transactions_root(fake_root).unwrap();
5528 assert_eq!(block.header.transactions_root, fake_root);
5529
5530 let block_opt = payload.into_block_raw_with_transactions_root_opt(Some(fake_root)).unwrap();
5531 assert_eq!(block_opt.header.transactions_root, fake_root);
5532 }
5533
5534 #[test]
5535 fn test_with_transactions_root_extra_data_validation() {
5536 let payload = ExecutionPayloadV1 {
5537 parent_hash: B256::default(),
5538 fee_recipient: Address::default(),
5539 state_root: B256::default(),
5540 receipts_root: B256::default(),
5541 logs_bloom: Bloom::default(),
5542 prev_randao: B256::default(),
5543 block_number: 0,
5544 gas_limit: 0,
5545 gas_used: 0,
5546 timestamp: 0,
5547 extra_data: Bytes::from(vec![0u8; MAXIMUM_EXTRA_DATA_SIZE + 1]),
5548 base_fee_per_gas: U256::from(1),
5549 block_hash: B256::default(),
5550 transactions: vec![],
5551 };
5552
5553 let fake_root = b256!("1111111111111111111111111111111111111111111111111111111111111111");
5554
5555 assert!(payload.clone().into_block_raw().is_err());
5556 assert!(payload.clone().into_block_raw_with_transactions_root(fake_root).is_err());
5557 assert!(payload.into_block_raw_with_transactions_root_opt(Some(fake_root)).is_err());
5558 }
5559
5560 #[test]
5561 fn test_decoded_transactions() {
5562 let transaction = Bytes::from_static(&hex!("f86d0a8458b20efd825208946177843db3138ae69679a54b95cf345ed759450d8806f3e8d87878800080820a95a0f8bddb1dcc4558b532ff747760a6f547dd275afdbe7bdecc90680e71de105757a014f34ba38c180913c0543b0ac2eccfb77cc3f801a535008dc50e533fbe435f53"));
5563
5564 let payload = ExecutionPayload::V1(ExecutionPayloadV1 {
5565 parent_hash: B256::default(),
5566 fee_recipient: Address::default(),
5567 state_root: B256::default(),
5568 receipts_root: B256::default(),
5569 logs_bloom: Bloom::default(),
5570 prev_randao: B256::default(),
5571 block_number: 0,
5572 gas_limit: 0,
5573 gas_used: 0,
5574 timestamp: 0,
5575 extra_data: Bytes::default(),
5576 base_fee_per_gas: U256::default(),
5577 block_hash: B256::default(),
5578 transactions: vec![transaction.clone()],
5579 });
5580
5581 let decoded: Vec<_> = payload.decoded_transactions::<TxEnvelope>().collect();
5583 assert_eq!(decoded.len(), 1);
5584 assert!(decoded[0].is_ok(), "Failed to decode transaction: {:?}", decoded[0]);
5585
5586 let decoded_with_encoded: Vec<_> =
5588 payload.decoded_transactions_with_encoded::<TxEnvelope>().collect();
5589 assert_eq!(decoded_with_encoded.len(), 1);
5590 assert!(decoded_with_encoded[0].is_ok());
5591 if let Ok(with_encoded) = &decoded_with_encoded[0] {
5592 assert_eq!(with_encoded.encoded_bytes(), &transaction);
5593 }
5594 }
5595
5596 #[test]
5597 #[cfg(feature = "serde")]
5598 fn serde_payload_attributes_without_slot_number() {
5599 let json = r#"{
5600 "timestamp": "0x1234",
5601 "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000",
5602 "suggestedFeeRecipient": "0x0000000000000000000000000000000000000000"
5603 }"#;
5604
5605 let attrs: PayloadAttributes = serde_json::from_str(json).unwrap();
5606 assert_eq!(attrs.timestamp, 0x1234);
5607 assert!(attrs.slot_number.is_none());
5608 assert!(attrs.target_gas_limit.is_none());
5609 }
5610
5611 #[test]
5612 #[cfg(feature = "serde")]
5613 fn serde_payload_attributes_with_hex_amsterdam_fields() {
5614 let json = r#"{
5615 "timestamp": "0x2",
5616 "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000",
5617 "suggestedFeeRecipient": "0x0000000000000000000000000000000000000000",
5618 "withdrawals": [],
5619 "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
5620 "slotNumber": "0x0",
5621 "targetGasLimit": "0x1c9c380"
5622 }"#;
5623
5624 let attrs: PayloadAttributes = serde_json::from_str(json).unwrap();
5625 assert_eq!(attrs.timestamp, 0x2);
5626 assert_eq!(attrs.slot_number, Some(0));
5627 assert_eq!(attrs.target_gas_limit, Some(30_000_000));
5628 }
5629
5630 #[test]
5631 #[cfg(feature = "serde")]
5632 fn serde_execution_payload_body_v2() {
5633 let body = ExecutionPayloadBodyV2 {
5634 transactions: vec![Bytes::from(vec![0x01, 0x02, 0x03])],
5635 withdrawals: Some(vec![Withdrawal {
5636 index: 1,
5637 validator_index: 2,
5638 address: Address::default(),
5639 amount: 100,
5640 }]),
5641 block_access_list: Some(Bytes::from(vec![0xaa, 0xbb, 0xcc])),
5642 };
5643
5644 let serialized = serde_json::to_string(&body).unwrap();
5645 let deserialized: ExecutionPayloadBodyV2 = serde_json::from_str(&serialized).unwrap();
5646 assert_eq!(deserialized, body);
5647 }
5648
5649 #[test]
5650 #[cfg(feature = "serde")]
5651 fn serde_execution_payload_body_v2_null_fields() {
5652 let body = ExecutionPayloadBodyV2 {
5653 transactions: vec![],
5654 withdrawals: None,
5655 block_access_list: None,
5656 };
5657
5658 let serialized = serde_json::to_string(&body).unwrap();
5659 let deserialized: ExecutionPayloadBodyV2 = serde_json::from_str(&serialized).unwrap();
5660 assert_eq!(deserialized, body);
5661 }
5662
5663 #[test]
5664 #[cfg(feature = "ssz")]
5665 fn ssz_execution_payload_body_v1_roundtrip() {
5666 use ssz::{Decode, Encode};
5667
5668 let body = ExecutionPayloadBodyV1 {
5669 transactions: vec![Bytes::from(vec![0x01, 0x02, 0x03])],
5670 withdrawals: Some(vec![Withdrawal {
5671 index: 1,
5672 validator_index: 2,
5673 address: Address::with_last_byte(3),
5674 amount: 4,
5675 }]),
5676 };
5677
5678 let decoded = ExecutionPayloadBodyV1::from_ssz_bytes(&body.as_ssz_bytes()).unwrap();
5679 assert_eq!(decoded, body);
5680
5681 let bodies: ExecutionPayloadBodiesV1 = vec![Some(body), None];
5682 let decoded = ExecutionPayloadBodiesV1::from_ssz_bytes(&bodies.as_ssz_bytes()).unwrap();
5683 assert_eq!(decoded, bodies);
5684 }
5685
5686 #[test]
5687 #[cfg(feature = "ssz")]
5688 fn ssz_execution_payload_body_v2_roundtrip() {
5689 use ssz::{Decode, Encode};
5690
5691 let body = ExecutionPayloadBodyV2 {
5692 transactions: vec![Bytes::from(vec![0x04, 0x05, 0x06])],
5693 withdrawals: None,
5694 block_access_list: Some(Bytes::from(vec![0xaa, 0xbb, 0xcc])),
5695 };
5696
5697 let decoded = ExecutionPayloadBodyV2::from_ssz_bytes(&body.as_ssz_bytes()).unwrap();
5698 assert_eq!(decoded, body);
5699
5700 let bodies: ExecutionPayloadBodiesV2 = vec![Some(body), None];
5701 let decoded = ExecutionPayloadBodiesV2::from_ssz_bytes(&bodies.as_ssz_bytes()).unwrap();
5702 assert_eq!(decoded, bodies);
5703 }
5704
5705 #[test]
5706 fn execution_payload_body_v1_to_v2_conversion() {
5707 let v1 = ExecutionPayloadBodyV1 {
5708 transactions: vec![Bytes::from(vec![0x01, 0x02])],
5709 withdrawals: Some(vec![Withdrawal {
5710 index: 1,
5711 validator_index: 2,
5712 address: Address::default(),
5713 amount: 100,
5714 }]),
5715 };
5716
5717 let v2: ExecutionPayloadBodyV2 = v1.clone().into();
5718 assert_eq!(v2.transactions, v1.transactions);
5719 assert_eq!(v2.withdrawals, v1.withdrawals);
5720 assert_eq!(v2.block_access_list, None);
5721 }
5722
5723 #[test]
5724 fn execution_payload_body_v2_to_v1_conversion() {
5725 let v2 = ExecutionPayloadBodyV2 {
5726 transactions: vec![Bytes::from(vec![0x01, 0x02])],
5727 withdrawals: Some(vec![Withdrawal {
5728 index: 1,
5729 validator_index: 2,
5730 address: Address::default(),
5731 amount: 100,
5732 }]),
5733 block_access_list: Some(Bytes::from(vec![0xaa, 0xbb])),
5734 };
5735
5736 let v1: ExecutionPayloadBodyV1 = v2.clone().into();
5737 assert_eq!(v1.transactions, v2.transactions);
5738 assert_eq!(v1.withdrawals, v2.withdrawals);
5739 }
5740
5741 #[test]
5742 #[cfg(feature = "serde")]
5743 fn serde_roundtrip_payload_v2() {
5744 let payload = ExecutionPayloadV2 {
5745 payload_inner: ExecutionPayloadV1 {
5746 parent_hash: B256::default(),
5747 fee_recipient: Address::default(),
5748 state_root: B256::default(),
5749 receipts_root: B256::default(),
5750 logs_bloom: Bloom::default(),
5751 prev_randao: B256::default(),
5752 block_number: 1,
5753 gas_limit: 30_000_000,
5754 gas_used: 21000,
5755 timestamp: 1234,
5756 extra_data: Bytes::default(),
5757 base_fee_per_gas: U256::from(7u64),
5758 block_hash: B256::default(),
5759 transactions: vec![],
5760 },
5761 withdrawals: vec![Withdrawal {
5762 index: 1,
5763 validator_index: 2,
5764 address: Address::default(),
5765 amount: 100,
5766 }],
5767 };
5768
5769 let serialized = serde_json::to_string(&payload).unwrap();
5770 let deserialized: ExecutionPayloadV2 = serde_json::from_str(&serialized).unwrap();
5771 assert_eq!(payload, deserialized);
5772 }
5773
5774 #[test]
5775 #[cfg(feature = "serde")]
5776 fn serde_roundtrip_payload_v4() {
5777 let payload = ExecutionPayloadV4 {
5778 payload_inner: ExecutionPayloadV3 {
5779 payload_inner: ExecutionPayloadV2 {
5780 payload_inner: ExecutionPayloadV1 {
5781 parent_hash: B256::default(),
5782 fee_recipient: Address::default(),
5783 state_root: B256::default(),
5784 receipts_root: B256::default(),
5785 logs_bloom: Bloom::default(),
5786 prev_randao: B256::default(),
5787 block_number: 1,
5788 gas_limit: 30_000_000,
5789 gas_used: 21000,
5790 timestamp: 1234,
5791 extra_data: Bytes::default(),
5792 base_fee_per_gas: U256::from(7u64),
5793 block_hash: B256::default(),
5794 transactions: vec![],
5795 },
5796 withdrawals: vec![],
5797 },
5798 blob_gas_used: 0,
5799 excess_blob_gas: 0,
5800 },
5801 block_access_list: Bytes::from(vec![0xaa, 0xbb]),
5802 slot_number: 0,
5803 };
5804
5805 let serialized = serde_json::to_string(&payload).unwrap();
5806 let deserialized: ExecutionPayloadV4 = serde_json::from_str(&serialized).unwrap();
5807 assert_eq!(payload, deserialized);
5808 }
5809
5810 #[test]
5811 fn payload_v4_from_block_falls_back_to_bal_hash_bytes() {
5812 let bal_hash = b256!("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef");
5813 let header = Header {
5814 block_access_list_hash: Some(bal_hash),
5815 slot_number: Some(7),
5816 ..Default::default()
5817 };
5818
5819 let block: Block<TxEnvelope> = Block::new(header, BlockBody::default());
5820 let (payload, _) = ExecutionPayload::from_block_unchecked(B256::with_last_byte(1), &block);
5821
5822 let payload = payload.as_v4().expect("expected V4 payload");
5823 assert_eq!(payload.block_access_list, Bytes::copy_from_slice(bal_hash.as_slice()));
5824 assert_eq!(payload.slot_number, 7);
5825 }
5826
5827 #[test]
5828 fn payload_v4_from_block_without_bal_hash_uses_empty_bal_hash_bytes() {
5829 let header = Header { slot_number: Some(3), ..Default::default() };
5830
5831 let block: Block<TxEnvelope> = Block::new(header, BlockBody::default());
5832 let payload = ExecutionPayloadV4::from_block_unchecked(B256::with_last_byte(2), &block);
5833
5834 assert_eq!(
5835 payload.block_access_list,
5836 Bytes::copy_from_slice(EMPTY_BLOCK_ACCESS_LIST_HASH.as_slice())
5837 );
5838 assert_eq!(payload.slot_number, 3);
5839 }
5840
5841 #[test]
5842 fn execution_data_from_sealed_block_uses_sealed_hash() {
5843 let block: Block<TxEnvelope> = Block::new(Header::default(), BlockBody::default());
5844 let block_hash = B256::with_last_byte(3);
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_ref_uses_sealed_hash() {
5853 let block: Block<TxEnvelope> = Block::new(Header::default(), BlockBody::default());
5854 let block_hash = B256::with_last_byte(4);
5855
5856 let execution_data = ExecutionData::from(Sealed::new_unchecked(&block, block_hash));
5857
5858 assert_eq!(execution_data.block_hash(), block_hash);
5859 }
5860
5861 #[test]
5862 fn execution_data_from_sealed_block_with_extras_preserves_bal() {
5863 let block_access_list = Bytes::from(vec![0xaa, 0xbb, 0xcc]);
5864 let header = Header {
5865 block_access_list_hash: Some(keccak256(&block_access_list)),
5866 slot_number: Some(7),
5867 ..Default::default()
5868 };
5869
5870 let block: Block<TxEnvelope> = Block::new(header, BlockBody::default());
5871 let block_hash = B256::with_last_byte(5);
5872 let execution_data = ExecutionData::from((
5873 Sealed::new_unchecked(block, block_hash),
5874 PayloadExtras::from(block_access_list.clone()),
5875 ));
5876
5877 assert_eq!(execution_data.block_hash(), block_hash);
5878 assert_eq!(execution_data.payload.block_access_list(), Some(&block_access_list));
5879 assert_eq!(execution_data.payload.slot_number(), Some(7));
5880 }
5881
5882 #[test]
5883 fn execution_data_from_sealed_block_ref_with_extras_preserves_bal() {
5884 let block_access_list = Bytes::from(vec![0xaa, 0xbb, 0xcc]);
5885 let header = Header {
5886 block_access_list_hash: Some(keccak256(&block_access_list)),
5887 slot_number: Some(7),
5888 ..Default::default()
5889 };
5890
5891 let block: Block<TxEnvelope> = Block::new(header, BlockBody::default());
5892 let block_hash = B256::with_last_byte(6);
5893 let execution_data = ExecutionData::from((
5894 Sealed::new_unchecked(&block, block_hash),
5895 PayloadExtras::from(block_access_list.clone()),
5896 ));
5897
5898 assert_eq!(execution_data.block_hash(), block_hash);
5899 assert_eq!(execution_data.payload.block_access_list(), Some(&block_access_list));
5900 assert_eq!(execution_data.payload.slot_number(), Some(7));
5901 }
5902
5903 #[test]
5904 fn execution_payload_gets_bal_hash_and_slot_number_from_v4() {
5905 let block_access_list = Bytes::from(vec![0xaa, 0xbb, 0xcc]);
5906 let payload = ExecutionPayload::from(ExecutionPayloadV4 {
5907 payload_inner: ExecutionPayloadV3 {
5908 payload_inner: ExecutionPayloadV2 {
5909 payload_inner: ExecutionPayloadV1 {
5910 parent_hash: B256::default(),
5911 fee_recipient: Address::default(),
5912 state_root: B256::default(),
5913 receipts_root: B256::default(),
5914 logs_bloom: Bloom::default(),
5915 prev_randao: B256::default(),
5916 block_number: 1,
5917 gas_limit: 30_000_000,
5918 gas_used: 21_000,
5919 timestamp: 1_234,
5920 extra_data: Bytes::default(),
5921 base_fee_per_gas: U256::ZERO,
5922 block_hash: B256::default(),
5923 transactions: vec![],
5924 },
5925 withdrawals: vec![],
5926 },
5927 blob_gas_used: 0,
5928 excess_blob_gas: 0,
5929 },
5930 block_access_list: block_access_list.clone(),
5931 slot_number: 7,
5932 });
5933
5934 assert_eq!(payload.slot_number(), Some(7));
5935 assert_eq!(payload.bal_hash(), Some(keccak256(&block_access_list)));
5936 }
5937
5938 #[test]
5939 #[cfg(feature = "serde")]
5940 fn serde_roundtrip_payload_input_v2_with_withdrawals() {
5941 let payload = ExecutionPayloadInputV2 {
5942 execution_payload: ExecutionPayloadV1 {
5943 parent_hash: B256::default(),
5944 fee_recipient: Address::default(),
5945 state_root: B256::default(),
5946 receipts_root: B256::default(),
5947 logs_bloom: Bloom::default(),
5948 prev_randao: B256::default(),
5949 block_number: 1,
5950 gas_limit: 30_000_000,
5951 gas_used: 21000,
5952 timestamp: 1234,
5953 extra_data: Bytes::default(),
5954 base_fee_per_gas: U256::from(7u64),
5955 block_hash: B256::default(),
5956 transactions: vec![],
5957 },
5958 withdrawals: Some(vec![]),
5959 };
5960
5961 let serialized = serde_json::to_string(&payload).unwrap();
5962 let deserialized: ExecutionPayloadInputV2 = serde_json::from_str(&serialized).unwrap();
5963 assert_eq!(payload, deserialized);
5964 }
5965
5966 #[test]
5967 #[cfg(feature = "serde")]
5968 fn serde_roundtrip_payload_input_v2_without_withdrawals() {
5969 let payload = ExecutionPayloadInputV2 {
5970 execution_payload: ExecutionPayloadV1 {
5971 parent_hash: B256::default(),
5972 fee_recipient: Address::default(),
5973 state_root: B256::default(),
5974 receipts_root: B256::default(),
5975 logs_bloom: Bloom::default(),
5976 prev_randao: B256::default(),
5977 block_number: 1,
5978 gas_limit: 30_000_000,
5979 gas_used: 21000,
5980 timestamp: 1234,
5981 extra_data: Bytes::default(),
5982 base_fee_per_gas: U256::from(7u64),
5983 block_hash: B256::default(),
5984 transactions: vec![],
5985 },
5986 withdrawals: None,
5987 };
5988
5989 let serialized = serde_json::to_string(&payload).unwrap();
5990 let deserialized: ExecutionPayloadInputV2 = serde_json::from_str(&serialized).unwrap();
5991 assert_eq!(payload, deserialized);
5992 }
5993
5994 #[test]
5995 #[cfg(feature = "serde")]
5996 fn serde_roundtrip_envelope_v4() {
5997 let envelope = ExecutionPayloadEnvelopeV4 {
5998 envelope_inner: ExecutionPayloadEnvelopeV3 {
5999 execution_payload: ExecutionPayloadV3 {
6000 payload_inner: ExecutionPayloadV2 {
6001 payload_inner: ExecutionPayloadV1 {
6002 parent_hash: B256::default(),
6003 fee_recipient: Address::default(),
6004 state_root: B256::default(),
6005 receipts_root: B256::default(),
6006 logs_bloom: Bloom::default(),
6007 prev_randao: B256::default(),
6008 block_number: 1,
6009 gas_limit: 30_000_000,
6010 gas_used: 21000,
6011 timestamp: 1234,
6012 extra_data: Bytes::default(),
6013 base_fee_per_gas: U256::from(7u64),
6014 block_hash: B256::default(),
6015 transactions: vec![],
6016 },
6017 withdrawals: vec![],
6018 },
6019 blob_gas_used: 0,
6020 excess_blob_gas: 0,
6021 },
6022 block_value: U256::from(1u64),
6023 blobs_bundle: BlobsBundleV1::empty(),
6024 should_override_builder: false,
6025 },
6026 execution_requests: Default::default(),
6027 };
6028
6029 let serialized = serde_json::to_string(&envelope).unwrap();
6030 let deserialized: ExecutionPayloadEnvelopeV4 = serde_json::from_str(&serialized).unwrap();
6031 assert_eq!(envelope, deserialized);
6032 }
6033
6034 #[test]
6035 #[cfg(feature = "serde")]
6036 fn serde_v3_with_many_transactions() {
6037 let tx = Bytes::from_static(&hex!("f865808506fc23ac00830124f8940000000000000000000000000000000000000316018032a044b25a8b9b247d01586b3d59c71728ff49c9b84928d9e7fa3377ead3b5570b5da03ceac696601ff7ee6f5fe8864e2998db9babdf5eeba1a0cd5b4d44b3fcbd181b"));
6038 let transactions: Vec<Bytes> = (0..100).map(|_| tx.clone()).collect();
6039
6040 let payload = ExecutionPayloadV3 {
6041 payload_inner: ExecutionPayloadV2 {
6042 payload_inner: ExecutionPayloadV1 {
6043 parent_hash: B256::default(),
6044 fee_recipient: Address::default(),
6045 state_root: B256::default(),
6046 receipts_root: B256::default(),
6047 logs_bloom: Bloom::default(),
6048 prev_randao: B256::default(),
6049 block_number: 1,
6050 gas_limit: 30_000_000,
6051 gas_used: 2_100_000,
6052 timestamp: 1234,
6053 extra_data: Bytes::default(),
6054 base_fee_per_gas: U256::from(7u64),
6055 block_hash: B256::default(),
6056 transactions,
6057 },
6058 withdrawals: vec![],
6059 },
6060 blob_gas_used: 0,
6061 excess_blob_gas: 0,
6062 };
6063
6064 let serialized = serde_json::to_string(&payload).unwrap();
6065 let deserialized: ExecutionPayloadV3 = serde_json::from_str(&serialized).unwrap();
6066 assert_eq!(payload, deserialized);
6067 }
6068
6069 #[test]
6070 #[cfg(feature = "serde")]
6071 fn serde_input_v2_rejects_unknown_fields() {
6072 let input = r#"{
6073 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
6074 "feeRecipient": "0x0000000000000000000000000000000000000000",
6075 "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
6076 "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
6077 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
6078 "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000",
6079 "blockNumber": "0x1",
6080 "gasLimit": "0x1c9c380",
6081 "gasUsed": "0x0",
6082 "timestamp": "0x1235",
6083 "extraData": "0x",
6084 "baseFeePerGas": "0x7",
6085 "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
6086 "transactions": [],
6087 "unknownField": "should fail"
6088 }"#;
6089
6090 let result: Result<ExecutionPayloadInputV2, _> = serde_json::from_str(input);
6091 assert!(result.is_err());
6092 }
6093}