1use crate::{withdrawals::BeaconWithdrawal, BlsPublicKey};
12use alloy_eips::eip4895::Withdrawal;
13use alloy_primitives::{Address, Bloom, Bytes, B256, U256};
14use alloy_rpc_types_engine::{
15 ExecutionPayload, ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3,
16 ExecutionPayloadV4,
17};
18use serde::{Deserialize, Deserializer, Serialize, Serializer};
19use serde_with::{serde_as, DeserializeAs, DisplayFromStr, SerializeAs};
20use std::borrow::Cow;
21
22#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26pub struct GetExecutionPayloadHeaderResponse {
27 pub version: String,
29 pub data: ExecutionPayloadHeaderData,
31}
32
33#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38pub struct ExecutionPayloadHeaderData {
39 pub message: ExecutionPayloadHeaderMessage,
41 pub signature: Bytes,
43}
44
45#[serde_as]
50#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
51pub struct ExecutionPayloadHeaderMessage {
52 pub header: ExecutionPayloadHeader,
54 #[serde_as(as = "DisplayFromStr")]
56 pub value: U256,
57 pub pubkey: BlsPublicKey,
59}
60
61#[serde_as]
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67pub struct BeaconBlockData {
68 pub message: BeaconBlockMessage,
70 pub signature: Bytes,
72}
73
74#[serde_as]
76#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77pub struct BeaconBlockMessage {
78 pub slot: String,
80 pub proposer_index: String,
82 pub parent_root: String,
84 pub state_root: String,
86 pub body: BeaconBlockBody,
88}
89
90#[serde_as]
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
93pub struct BeaconBlockBody {
94 #[serde(
96 serialize_with = "beacon_payload::serialize",
97 deserialize_with = "beacon_payload::deserialize"
98 )]
99 pub execution_payload: ExecutionPayload,
100}
101
102impl BeaconBlockData {
103 pub const fn execution_payload(&self) -> &ExecutionPayload {
105 &self.message.body.execution_payload
106 }
107}
108
109#[serde_as]
111#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
112pub struct ExecutionPayloadHeader {
113 pub parent_hash: B256,
115 pub fee_recipient: Address,
117 pub state_root: B256,
119 pub receipts_root: B256,
121 pub logs_bloom: Bloom,
123 pub prev_randao: B256,
125 #[serde_as(as = "DisplayFromStr")]
127 pub block_number: u64,
128 #[serde_as(as = "DisplayFromStr")]
130 pub gas_limit: u64,
131 #[serde_as(as = "DisplayFromStr")]
133 pub gas_used: u64,
134 #[serde_as(as = "DisplayFromStr")]
136 pub timestamp: u64,
137 pub extra_data: Bytes,
139 #[serde_as(as = "DisplayFromStr")]
141 pub base_fee_per_gas: U256,
142 pub block_hash: B256,
144 pub transactions_root: B256,
146}
147
148#[serde_as]
149#[derive(Serialize, Deserialize)]
150struct BeaconPayloadAttributes {
151 #[serde_as(as = "DisplayFromStr")]
152 timestamp: u64,
153 prev_randao: B256,
154 suggested_fee_recipient: Address,
155 #[serde(skip_serializing_if = "Option::is_none")]
156 #[serde_as(as = "Option<Vec<BeaconWithdrawal>>")]
157 withdrawals: Option<Vec<Withdrawal>>,
158 #[serde(skip_serializing_if = "Option::is_none")]
159 parent_beacon_block_root: Option<B256>,
160 #[serde(skip_serializing_if = "Option::is_none")]
161 pub slot_number: Option<u64>,
162}
163
164pub mod beacon_api_payload_attributes {
171 use super::*;
172 use alloy_rpc_types_engine::PayloadAttributes;
173
174 pub fn serialize<S>(
176 payload_attributes: &PayloadAttributes,
177 serializer: S,
178 ) -> Result<S::Ok, S::Error>
179 where
180 S: Serializer,
181 {
182 let beacon_api_payload_attributes = BeaconPayloadAttributes {
183 timestamp: payload_attributes.timestamp,
184 prev_randao: payload_attributes.prev_randao,
185 suggested_fee_recipient: payload_attributes.suggested_fee_recipient,
186 withdrawals: payload_attributes.withdrawals.clone(),
187 parent_beacon_block_root: payload_attributes.parent_beacon_block_root,
188 slot_number: payload_attributes.slot_number,
189 };
190 beacon_api_payload_attributes.serialize(serializer)
191 }
192
193 pub fn deserialize<'de, D>(deserializer: D) -> Result<PayloadAttributes, D::Error>
195 where
196 D: Deserializer<'de>,
197 {
198 let beacon_api_payload_attributes = BeaconPayloadAttributes::deserialize(deserializer)?;
199 Ok(PayloadAttributes {
200 timestamp: beacon_api_payload_attributes.timestamp,
201 prev_randao: beacon_api_payload_attributes.prev_randao,
202 suggested_fee_recipient: beacon_api_payload_attributes.suggested_fee_recipient,
203 withdrawals: beacon_api_payload_attributes.withdrawals,
204 parent_beacon_block_root: beacon_api_payload_attributes.parent_beacon_block_root,
205 slot_number: beacon_api_payload_attributes.slot_number,
206 })
207 }
208}
209
210#[serde_as]
211#[derive(Debug, Serialize, Deserialize)]
212struct BeaconExecutionPayloadV1<'a> {
213 parent_hash: Cow<'a, B256>,
214 fee_recipient: Cow<'a, Address>,
215 state_root: Cow<'a, B256>,
216 receipts_root: Cow<'a, B256>,
217 logs_bloom: Cow<'a, Bloom>,
218 prev_randao: Cow<'a, B256>,
219 #[serde_as(as = "DisplayFromStr")]
220 block_number: u64,
221 #[serde_as(as = "DisplayFromStr")]
222 gas_limit: u64,
223 #[serde_as(as = "DisplayFromStr")]
224 gas_used: u64,
225 #[serde_as(as = "DisplayFromStr")]
226 timestamp: u64,
227 extra_data: Cow<'a, Bytes>,
228 #[serde_as(as = "DisplayFromStr")]
229 base_fee_per_gas: U256,
230 block_hash: Cow<'a, B256>,
231 transactions: Cow<'a, [Bytes]>,
232}
233
234impl<'a> From<BeaconExecutionPayloadV1<'a>> for ExecutionPayloadV1 {
235 fn from(payload: BeaconExecutionPayloadV1<'a>) -> Self {
236 let BeaconExecutionPayloadV1 {
237 parent_hash,
238 fee_recipient,
239 state_root,
240 receipts_root,
241 logs_bloom,
242 prev_randao,
243 block_number,
244 gas_limit,
245 gas_used,
246 timestamp,
247 extra_data,
248 base_fee_per_gas,
249 block_hash,
250 transactions,
251 } = payload;
252 Self {
253 parent_hash: parent_hash.into_owned(),
254 fee_recipient: fee_recipient.into_owned(),
255 state_root: state_root.into_owned(),
256 receipts_root: receipts_root.into_owned(),
257 logs_bloom: logs_bloom.into_owned(),
258 prev_randao: prev_randao.into_owned(),
259 block_number,
260 gas_limit,
261 gas_used,
262 timestamp,
263 extra_data: extra_data.into_owned(),
264 base_fee_per_gas,
265 block_hash: block_hash.into_owned(),
266 transactions: transactions.into_owned(),
267 }
268 }
269}
270
271impl<'a> From<&'a ExecutionPayloadV1> for BeaconExecutionPayloadV1<'a> {
272 fn from(value: &'a ExecutionPayloadV1) -> Self {
273 let ExecutionPayloadV1 {
274 parent_hash,
275 fee_recipient,
276 state_root,
277 receipts_root,
278 logs_bloom,
279 prev_randao,
280 block_number,
281 gas_limit,
282 gas_used,
283 timestamp,
284 extra_data,
285 base_fee_per_gas,
286 block_hash,
287 transactions,
288 } = value;
289
290 BeaconExecutionPayloadV1 {
291 parent_hash: Cow::Borrowed(parent_hash),
292 fee_recipient: Cow::Borrowed(fee_recipient),
293 state_root: Cow::Borrowed(state_root),
294 receipts_root: Cow::Borrowed(receipts_root),
295 logs_bloom: Cow::Borrowed(logs_bloom),
296 prev_randao: Cow::Borrowed(prev_randao),
297 block_number: *block_number,
298 gas_limit: *gas_limit,
299 gas_used: *gas_used,
300 timestamp: *timestamp,
301 extra_data: Cow::Borrowed(extra_data),
302 base_fee_per_gas: *base_fee_per_gas,
303 block_hash: Cow::Borrowed(block_hash),
304 transactions: Cow::Borrowed(transactions),
305 }
306 }
307}
308
309pub mod beacon_payload_v1 {
312 use super::*;
313
314 pub fn serialize<S>(
316 payload_attributes: &ExecutionPayloadV1,
317 serializer: S,
318 ) -> Result<S::Ok, S::Error>
319 where
320 S: Serializer,
321 {
322 BeaconExecutionPayloadV1::from(payload_attributes).serialize(serializer)
323 }
324
325 pub fn deserialize<'de, D>(deserializer: D) -> Result<ExecutionPayloadV1, D::Error>
327 where
328 D: Deserializer<'de>,
329 {
330 BeaconExecutionPayloadV1::deserialize(deserializer).map(Into::into)
331 }
332}
333
334#[serde_as]
335#[derive(Debug, Serialize, Deserialize)]
336struct BeaconExecutionPayloadV2<'a> {
337 #[serde(flatten)]
339 payload_inner: BeaconExecutionPayloadV1<'a>,
340 #[serde_as(as = "Vec<BeaconWithdrawal>")]
343 withdrawals: Vec<Withdrawal>,
344}
345
346impl<'a> From<BeaconExecutionPayloadV2<'a>> for ExecutionPayloadV2 {
347 fn from(payload: BeaconExecutionPayloadV2<'a>) -> Self {
348 let BeaconExecutionPayloadV2 { payload_inner, withdrawals } = payload;
349 Self { payload_inner: payload_inner.into(), withdrawals }
350 }
351}
352
353impl<'a> From<&'a ExecutionPayloadV2> for BeaconExecutionPayloadV2<'a> {
354 fn from(value: &'a ExecutionPayloadV2) -> Self {
355 let ExecutionPayloadV2 { payload_inner, withdrawals } = value;
356 BeaconExecutionPayloadV2 {
357 payload_inner: payload_inner.into(),
358 withdrawals: withdrawals.clone(),
359 }
360 }
361}
362
363pub mod beacon_payload_v2 {
366 use super::*;
367
368 pub fn serialize<S>(
370 payload_attributes: &ExecutionPayloadV2,
371 serializer: S,
372 ) -> Result<S::Ok, S::Error>
373 where
374 S: Serializer,
375 {
376 BeaconExecutionPayloadV2::from(payload_attributes).serialize(serializer)
377 }
378
379 pub fn deserialize<'de, D>(deserializer: D) -> Result<ExecutionPayloadV2, D::Error>
381 where
382 D: Deserializer<'de>,
383 {
384 BeaconExecutionPayloadV2::deserialize(deserializer).map(Into::into)
385 }
386}
387
388#[serde_as]
389#[derive(Debug, Serialize, Deserialize)]
390struct BeaconExecutionPayloadV3<'a> {
391 #[serde(flatten)]
393 payload_inner: BeaconExecutionPayloadV2<'a>,
394 #[serde_as(as = "DisplayFromStr")]
395 blob_gas_used: u64,
396 #[serde_as(as = "DisplayFromStr")]
397 excess_blob_gas: u64,
398}
399
400impl<'a> From<BeaconExecutionPayloadV3<'a>> for ExecutionPayloadV3 {
401 fn from(payload: BeaconExecutionPayloadV3<'a>) -> Self {
402 let BeaconExecutionPayloadV3 { payload_inner, blob_gas_used, excess_blob_gas } = payload;
403 Self { payload_inner: payload_inner.into(), blob_gas_used, excess_blob_gas }
404 }
405}
406
407impl<'a> From<&'a ExecutionPayloadV3> for BeaconExecutionPayloadV3<'a> {
408 fn from(value: &'a ExecutionPayloadV3) -> Self {
409 let ExecutionPayloadV3 { payload_inner, blob_gas_used, excess_blob_gas } = value;
410 BeaconExecutionPayloadV3 {
411 payload_inner: payload_inner.into(),
412 blob_gas_used: *blob_gas_used,
413 excess_blob_gas: *excess_blob_gas,
414 }
415 }
416}
417
418pub mod beacon_payload_v3 {
421 use super::*;
422
423 pub fn serialize<S>(
425 payload_attributes: &ExecutionPayloadV3,
426 serializer: S,
427 ) -> Result<S::Ok, S::Error>
428 where
429 S: Serializer,
430 {
431 BeaconExecutionPayloadV3::from(payload_attributes).serialize(serializer)
432 }
433
434 pub fn deserialize<'de, D>(deserializer: D) -> Result<ExecutionPayloadV3, D::Error>
436 where
437 D: Deserializer<'de>,
438 {
439 BeaconExecutionPayloadV3::deserialize(deserializer).map(Into::into)
440 }
441}
442
443#[serde_as]
447#[derive(Debug, Serialize, Deserialize)]
448struct BeaconExecutionPayloadV4<'a> {
449 #[serde(flatten)]
451 payload_inner: BeaconExecutionPayloadV3<'a>,
452 block_access_list: Cow<'a, Bytes>,
454 #[serde_as(as = "DisplayFromStr")]
456 slot_number: u64,
457}
458
459impl<'a> From<BeaconExecutionPayloadV4<'a>> for ExecutionPayloadV4 {
460 fn from(payload: BeaconExecutionPayloadV4<'a>) -> Self {
461 let BeaconExecutionPayloadV4 { payload_inner, block_access_list, slot_number } = payload;
462 Self {
463 payload_inner: payload_inner.into(),
464 block_access_list: block_access_list.into_owned(),
465 slot_number,
466 }
467 }
468}
469
470impl<'a> From<&'a ExecutionPayloadV4> for BeaconExecutionPayloadV4<'a> {
471 fn from(value: &'a ExecutionPayloadV4) -> Self {
472 let ExecutionPayloadV4 { payload_inner, block_access_list, slot_number } = value;
473 BeaconExecutionPayloadV4 {
474 payload_inner: payload_inner.into(),
475 block_access_list: Cow::Borrowed(block_access_list),
476 slot_number: *slot_number,
477 }
478 }
479}
480
481pub mod beacon_payload_v4 {
484 use super::*;
485
486 pub fn serialize<S>(
488 payload_attributes: &ExecutionPayloadV4,
489 serializer: S,
490 ) -> Result<S::Ok, S::Error>
491 where
492 S: Serializer,
493 {
494 BeaconExecutionPayloadV4::from(payload_attributes).serialize(serializer)
495 }
496
497 pub fn deserialize<'de, D>(deserializer: D) -> Result<ExecutionPayloadV4, D::Error>
499 where
500 D: Deserializer<'de>,
501 {
502 BeaconExecutionPayloadV4::deserialize(deserializer).map(Into::into)
503 }
504}
505
506#[derive(Debug, Serialize)]
508#[serde(untagged)]
509enum BeaconExecutionPayload<'a> {
510 V1(BeaconExecutionPayloadV1<'a>),
512 V2(BeaconExecutionPayloadV2<'a>),
514 V3(BeaconExecutionPayloadV3<'a>),
516 V4(BeaconExecutionPayloadV4<'a>),
518}
519
520impl<'de> Deserialize<'de> for BeaconExecutionPayload<'de> {
522 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
523 where
524 D: Deserializer<'de>,
525 {
526 #[serde_as]
527 #[derive(Deserialize)]
528 #[serde(untagged)]
529 enum BeaconExecutionPayloadDesc<'a> {
530 V4(BeaconExecutionPayloadV4<'a>),
531 V3(BeaconExecutionPayloadV3<'a>),
532 V2(BeaconExecutionPayloadV2<'a>),
533 V1(BeaconExecutionPayloadV1<'a>),
534 }
535 match BeaconExecutionPayloadDesc::deserialize(deserializer)? {
536 BeaconExecutionPayloadDesc::V4(payload) => Ok(Self::V4(payload)),
537 BeaconExecutionPayloadDesc::V3(payload) => Ok(Self::V3(payload)),
538 BeaconExecutionPayloadDesc::V2(payload) => Ok(Self::V2(payload)),
539 BeaconExecutionPayloadDesc::V1(payload) => Ok(Self::V1(payload)),
540 }
541 }
542}
543
544impl<'a> From<BeaconExecutionPayload<'a>> for ExecutionPayload {
545 fn from(payload: BeaconExecutionPayload<'a>) -> Self {
546 match payload {
547 BeaconExecutionPayload::V1(payload) => Self::V1(ExecutionPayloadV1::from(payload)),
548 BeaconExecutionPayload::V2(payload) => Self::V2(ExecutionPayloadV2::from(payload)),
549 BeaconExecutionPayload::V3(payload) => Self::V3(ExecutionPayloadV3::from(payload)),
550 BeaconExecutionPayload::V4(payload) => Self::V4(ExecutionPayloadV4::from(payload)),
551 }
552 }
553}
554
555impl<'a> From<&'a ExecutionPayload> for BeaconExecutionPayload<'a> {
556 fn from(value: &'a ExecutionPayload) -> Self {
557 match value {
558 ExecutionPayload::V1(payload) => {
559 BeaconExecutionPayload::V1(BeaconExecutionPayloadV1::from(payload))
560 }
561 ExecutionPayload::V2(payload) => {
562 BeaconExecutionPayload::V2(BeaconExecutionPayloadV2::from(payload))
563 }
564 ExecutionPayload::V3(payload) => {
565 BeaconExecutionPayload::V3(BeaconExecutionPayloadV3::from(payload))
566 }
567 ExecutionPayload::V4(payload) => {
568 BeaconExecutionPayload::V4(BeaconExecutionPayloadV4::from(payload))
569 }
570 }
571 }
572}
573
574impl SerializeAs<ExecutionPayload> for BeaconExecutionPayload<'_> {
575 fn serialize_as<S>(source: &ExecutionPayload, serializer: S) -> Result<S::Ok, S::Error>
576 where
577 S: Serializer,
578 {
579 beacon_payload::serialize(source, serializer)
580 }
581}
582
583impl<'de> DeserializeAs<'de, ExecutionPayload> for BeaconExecutionPayload<'de> {
584 fn deserialize_as<D>(deserializer: D) -> Result<ExecutionPayload, D::Error>
585 where
586 D: Deserializer<'de>,
587 {
588 beacon_payload::deserialize(deserializer)
589 }
590}
591
592pub mod beacon_payload {
595 use super::*;
596
597 pub fn serialize<S>(
599 payload_attributes: &ExecutionPayload,
600 serializer: S,
601 ) -> Result<S::Ok, S::Error>
602 where
603 S: Serializer,
604 {
605 BeaconExecutionPayload::from(payload_attributes).serialize(serializer)
606 }
607
608 pub fn deserialize<'de, D>(deserializer: D) -> Result<ExecutionPayload, D::Error>
610 where
611 D: Deserializer<'de>,
612 {
613 BeaconExecutionPayload::deserialize(deserializer).map(Into::into)
614 }
615}
616
617pub fn execution_payload_from_beacon_str(val: &str) -> Result<ExecutionPayload, serde_json::Error> {
620 #[derive(Deserialize)]
621 #[serde(transparent)]
622 struct E {
623 #[serde(deserialize_with = "beacon_payload::deserialize")]
624 payload: ExecutionPayload,
625 }
626 serde_json::from_str::<E>(val).map(|val| val.payload)
627}
628
629#[cfg(test)]
630mod tests {
631 use super::*;
632 use similar_asserts::assert_eq;
633
634 #[test]
635 fn serde_get_payload_header_response() {
636 let s = r#"{"version":"bellatrix","data":{"message":{"header":{"parent_hash":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","fee_recipient":"0xabcf8e0d4e9587369b2301d0790347320302cc09","state_root":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","receipts_root":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","logs_bloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prev_randao":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","block_number":"1","gas_limit":"1","gas_used":"1","timestamp":"1","extra_data":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","base_fee_per_gas":"1","block_hash":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","transactions_root":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"},"value":"1","pubkey":"0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"},"signature":"0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"}}"#;
637 let resp: GetExecutionPayloadHeaderResponse = serde_json::from_str(s).unwrap();
638 let json: serde_json::Value = serde_json::from_str(s).unwrap();
639 assert_eq!(json, serde_json::to_value(resp).unwrap());
640 }
641
642 #[test]
643 fn serde_payload_header() {
644 let s = r#"{"parent_hash":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","fee_recipient":"0xabcf8e0d4e9587369b2301d0790347320302cc09","state_root":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","receipts_root":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","logs_bloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prev_randao":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","block_number":"1","gas_limit":"1","gas_used":"1","timestamp":"1","extra_data":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","base_fee_per_gas":"1","block_hash":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","transactions_root":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"}"#;
645 let header: ExecutionPayloadHeader = serde_json::from_str(s).unwrap();
646 let json: serde_json::Value = serde_json::from_str(s).unwrap();
647 assert_eq!(json, serde_json::to_value(header).unwrap());
648 }
649
650 #[test]
651 fn test_execution_payload_from_beacon_str() {
652 let v1_payload_str = r#"{
654 "parent_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
655 "fee_recipient": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
656 "state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
657 "receipts_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
658 "logs_bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
659 "prev_randao": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
660 "block_number": "1",
661 "gas_limit": "1000",
662 "gas_used": "500",
663 "timestamp": "1234567890",
664 "extra_data": "0x",
665 "base_fee_per_gas": "1000000000",
666 "block_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
667 "transactions": ["0x02f878831469668303f51d843b9ac9f9843b9aca0082520894c93269b73096998db66be0441e836d873535cb9c8894a19041886f000080c001a031cc29234036afbf9a1fb9476b463367cb1f957ac0b919b69bbc798436e604aaa018c4e9c3914eb27aadd0b91e10b18655739fcf8c1fc398763a9f1beecb8ddc86"]
668 }"#;
669
670 let payload = execution_payload_from_beacon_str(v1_payload_str).unwrap();
671 match payload {
672 ExecutionPayload::V1(v1) => {
673 assert_eq!(v1.block_number, 1);
674 assert_eq!(v1.gas_limit, 1000);
675 assert_eq!(v1.gas_used, 500);
676 assert_eq!(v1.timestamp, 1234567890);
677 assert_eq!(v1.base_fee_per_gas, U256::from(1000000000u64));
678 assert_eq!(v1.transactions.len(), 1);
679 }
680 _ => panic!("Expected V1 payload"),
681 }
682
683 let v2_payload_str = r#"{
685 "parent_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
686 "fee_recipient": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
687 "state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
688 "receipts_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
689 "logs_bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
690 "prev_randao": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
691 "block_number": "2",
692 "gas_limit": "2000",
693 "gas_used": "1000",
694 "timestamp": "1234567891",
695 "extra_data": "0x",
696 "base_fee_per_gas": "2000000000",
697 "block_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
698 "transactions": [],
699 "withdrawals": [
700 {
701 "index": "0",
702 "validator_index": "0",
703 "address": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
704 "amount": "32000000000"
705 }
706 ]
707 }"#;
708
709 let payload = execution_payload_from_beacon_str(v2_payload_str).unwrap();
710 match payload {
711 ExecutionPayload::V2(v2) => {
712 assert_eq!(v2.payload_inner.block_number, 2);
713 assert_eq!(v2.payload_inner.gas_limit, 2000);
714 assert_eq!(v2.payload_inner.gas_used, 1000);
715 assert_eq!(v2.withdrawals.len(), 1);
716 assert_eq!(v2.withdrawals[0].index, 0);
717 assert_eq!(v2.withdrawals[0].amount, 32000000000);
718 }
719 _ => panic!("Expected V2 payload"),
720 }
721
722 let v3_payload_str = r#"{
724 "parent_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
725 "fee_recipient": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
726 "state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
727 "receipts_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
728 "logs_bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
729 "prev_randao": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
730 "block_number": "3",
731 "gas_limit": "3000",
732 "gas_used": "1500",
733 "timestamp": "1234567892",
734 "extra_data": "0x",
735 "base_fee_per_gas": "3000000000",
736 "block_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
737 "transactions": [],
738 "withdrawals": [],
739 "blob_gas_used": "131072",
740 "excess_blob_gas": "262144"
741 }"#;
742
743 let payload = execution_payload_from_beacon_str(v3_payload_str).unwrap();
744 match payload {
745 ExecutionPayload::V3(v3) => {
746 assert_eq!(v3.payload_inner.payload_inner.block_number, 3);
747 assert_eq!(v3.payload_inner.payload_inner.gas_limit, 3000);
748 assert_eq!(v3.payload_inner.payload_inner.gas_used, 1500);
749 assert_eq!(v3.blob_gas_used, 131072);
750 assert_eq!(v3.excess_blob_gas, 262144);
751 }
752 _ => panic!("Expected V3 payload"),
753 }
754
755 let invalid_json = r#"{ invalid json }"#;
757 assert!(execution_payload_from_beacon_str(invalid_json).is_err());
758 }
759
760 #[test]
761 fn test_extract_payload_from_beacon_block() {
762 let beacon_block_json = r#"{
764 "message": {
765 "slot": "12225729",
766 "proposer_index": "496520",
767 "parent_root": "0x462f4abf9b6881724e6489085b3bb3931312e31ffb43f7cec3d0ee624dc2b58e",
768 "state_root": "0x2c6e3ff0b0f7bc33b30a020e75e69c2bba26fb42a7e234e8275e655170925a71",
769 "body": {
770 "randao_reveal": "0x825dc181628713b55f40ed3f489be0c60f0513f88eecb25c7aa512ad24b912b3929bdf1930b50af4c18fb8b5f490352218a1c25adc01f7c3aaa50f982d762f589b4f5b6806e1d37e3f70af7afe990d1b1e8e337ac67b53bb7896f2052ecfccc1",
771 "eth1_data": {
772 "deposit_root": "0x2ebc563cabdbbacbc56f0de1d2d1c2d5315a4b071fcd8566aabbf0a45161c64e",
773 "deposit_count": "2045305",
774 "block_hash": "0x0958d83550263ff0d9f9a0bc5ea3cd2a136e0933b6f43cbb17f36e4da8d809b1"
775 },
776 "graffiti": "0x52502d4e502076312e31372e3000000000000000000000000000000000000000",
777 "proposer_slashings": [],
778 "attester_slashings": [],
779 "attestations": [],
780 "deposits": [],
781 "voluntary_exits": [],
782 "sync_aggregate": {
783 "sync_committee_bits": "0x71b7f7596e64ef7f7ef4f938e9f68abfbfe95bff09393315bb93bbec7f7ef27effa4c7f25ba7cbdb87efbbf73fdaebb9efefeb3ef7fff8effafdd7aff5677bfc",
784 "sync_committee_signature": "0xb45afdccf46b3518c295407594d82fcfd7fbff767f1b7bb2e7c9bdc8a0229232d201247b449d4bddf01fc974ce0b57601987fb401bb346062e53981cfb81dd6f9c519d645248a46ceba695c2d9630cfc68b26efc35f6ca14c49af9170581ad90"
785 },
786 "execution_payload": {
787 "parent_hash": "0x3a798cf01d2c58af71b4d00f6b343c1faa88a4e8350d763d181928205ece05fa",
788 "fee_recipient": "0xdadB0d80178819F2319190D340ce9A924f783711",
789 "state_root": "0xf258006fe790a654326ceb30933e4216cd8cc2087b16f5189c8ac316d22b918f",
790 "receipts_root": "0xf8e75ccce80b590f6ac30b859f308edab28c9d87ed8ad50d257902df4ba05ca5",
791 "logs_bloom": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
792 "prev_randao": "0x6a4900e9b3958061c0d7fff943a7cd75d9c13f1ba16bd4f9b7dd31b72a82cc11",
793 "block_number": "23003311",
794 "gas_limit": "45043901",
795 "gas_used": "41421505",
796 "timestamp": "1753532771",
797 "extra_data": "0x4275696c6465724e6574202842656176657229",
798 "base_fee_per_gas": "236192093",
799 "block_hash": "0xa46feca5c8c498c9bf9741f3716d935b25a1a7ff2961d5d1e692f1e97f93a2ca",
800 "transactions": [
801 "0x02f901540182e0948505dec6f0ec8505dec6f0ec8307a12094360e051a25ca6decd2f0e91ea4c179a96c0e565e80b8e4ccf22927000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000bc396689893d065f41bc2c6ecbee5e008523344700000000000000000000000000000000000000000000000012651a94c4e78f2200000000000000000000000000000000000000000000034519cd0a9daa3a356e000000000000000000000000cd83055557536eff25fd0eafbc56e74a1b4260b30000000000000000000000000000000000000000000000000000000000000bb80000000000000000000000000000000000000000000000000000000000000000c001a0c45c9362e16382b20cc8f04599743f8cdb52031868251c5e4baa8add569019f1a02558d71f50ed265f7d63dde46e8ae477d58d833c36dfe9b4e7eb8783732cbf63",
802 "0x02f90405018265c38084151e020b8303ca8894a69babef1ca67a37ffaf7a485dfff3382056e78c83db9700b9014478e111f600000000000000000000000039807fc9a64a376b99b1cebde2e79e3826d39aa1000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c42f1c6b50000000000000000000000000000000000000000000000000abd98bd97ba9e63c00000000000000000000000000000000000000000000000033f578d0b9b5b4000000000000000000000000000000000000000000000402d44ba9f99ee186b7d50000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006884c963ff8000000000000000000000000000000000000000000000000000000001227b00000000000000000000000000000000000000000000000000000000f90251d69439807fc9a64a376b99b1cebde2e79e3826d39aa1c0f85994c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a00cb865ff1951c90111975d77bc75fa8312f25b08bb19b908f6b9c43691ac0cafa075245230289a9f0bf73a6c59aef6651b98b3833a62a3c0bd9ab6b0dec8ed4d8ff8dd9411b815efb8f581194ae79006d24e0d814b7697f6f8c6a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000004a0000000000000000000000000000000000000000000000000000000000000001ba0000000000000000000000000000000000000000000000000000000000000001ca02f2606b2c0d121a5cc1b59088ba7234e9d1c805f41724c938a2661d69532e0e9f8fe94dac17f958d2ee523a2206206994597c13d831ec7f8e7a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000004a0000000000000000000000000000000000000000000000000000000000000000aa0169228ca33ea854d54aa1e506e59ec687f618a41074f5f5de937a0e9c6343e5aa035d7fb7665514f774d2c2df607e197eb8674b6e63d2638472758647a2e67406aa03ad2db55fe5657fe773e3b7111e43f4b662a181a20e875b3b8be52dd9f0e233380a00cc4b1ab039d330bff37a79b961104004056d1fdf24ecf370036b453fdd4f2c7a0197e3c66c1f3fbaf9cb07902e38ebb451fd9f98e5a5a283d21716cfe3dc761fa"
803 ]
804 }
805 }
806 },
807 "signature": "0x8a9cfe747dbb5d6ee1538638b2adfc304c8bcbeb03f489756ca7dc7a12081df892f38b924d19c9f5530c746b86a34beb019070bb7707de5a8efc8bdab8ca5668d7bb0e31c5ffd24913d23c80a6f6f70ba89e280dd46d19d6128ac7f42ffee93e"
808
809 }"#;
810
811 let beacon_block: BeaconBlockData =
812 serde_json::from_str(beacon_block_json).expect("Failed to deserialize beacon block");
813
814 let execution_payload = beacon_block.execution_payload();
815
816 match execution_payload {
817 ExecutionPayload::V1(v1) => {
818 assert_eq!(v1.block_number, 23003311);
819 assert_eq!(v1.gas_limit, 45043901);
820 assert_eq!(v1.gas_used, 41421505);
821 assert_eq!(v1.timestamp, 1753532771);
822 assert_eq!(
823 v1.parent_hash.to_string(),
824 "0x3a798cf01d2c58af71b4d00f6b343c1faa88a4e8350d763d181928205ece05fa"
825 );
826 assert_eq!(
827 v1.fee_recipient.to_string().to_lowercase(),
828 "0xdadb0d80178819f2319190d340ce9a924f783711"
829 );
830 assert_eq!(
831 v1.block_hash.to_string(),
832 "0xa46feca5c8c498c9bf9741f3716d935b25a1a7ff2961d5d1e692f1e97f93a2ca"
833 );
834
835 assert_eq!(v1.transactions.len(), 2);
837 assert!(v1.transactions[0].to_string().starts_with("0x02f901540182e094"));
838 }
839 ExecutionPayload::V2(_) => panic!("Expected V1 payload, got V2"),
840 ExecutionPayload::V3(_) => panic!("Expected V1 payload, got V3"),
841 ExecutionPayload::V4(_) => panic!("Expected V1 payload, got V4"),
842 }
843 }
844
845 #[test]
846 fn serde_beacon_payload_attributes_without_slot_number() {
847 use alloy_rpc_types_engine::PayloadAttributes;
848
849 let json = r#"{
850 "timestamp": "1234",
851 "prev_randao": "0x0000000000000000000000000000000000000000000000000000000000000000",
852 "suggested_fee_recipient": "0x0000000000000000000000000000000000000000"
853 }"#;
854
855 let attrs: BeaconPayloadAttributes = serde_json::from_str(json).unwrap();
856 assert_eq!(attrs.timestamp, 1234);
857 assert!(attrs.slot_number.is_none());
858
859 let engine_attrs = PayloadAttributes {
860 timestamp: attrs.timestamp,
861 prev_randao: attrs.prev_randao,
862 suggested_fee_recipient: attrs.suggested_fee_recipient,
863 withdrawals: attrs.withdrawals,
864 parent_beacon_block_root: attrs.parent_beacon_block_root,
865 slot_number: attrs.slot_number,
866 };
867 assert!(engine_attrs.slot_number.is_none());
868 }
869}