Skip to main content

alloy_rpc_types_beacon/
block.rs

1//! Beacon block types.
2//!
3//! See also <https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockV2>
4
5use crate::{header::BeaconBlockHeader, BlsPublicKey, BlsSignature};
6use alloy_primitives::{Bytes, B256};
7use serde::{Deserialize, Serialize};
8use serde_with::{serde_as, DisplayFromStr};
9
10/// Response from the [`/eth/v2/beacon/blocks/{block_id}`](https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockV2) endpoint.
11///
12/// See <https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockV2>
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub struct BlockResponse<T = serde_json::Value> {
15    /// The version of the block (e.g., "phase0", "altair", "bellatrix", "capella", "deneb",
16    /// "electra").
17    pub version: String,
18    /// True if the response references an unverified execution payload. Optimistic information may
19    /// be invalidated at a later time. If the field is not present, assume the False value.
20    #[serde(default)]
21    pub execution_optimistic: bool,
22    /// True if the response references the finalized history of the chain, as determined by fork
23    /// choice. If the field is not present, additional calls are necessary to compare the epoch of
24    /// the requested information with the finalized checkpoint.
25    #[serde(default)]
26    pub finalized: bool,
27    /// The signed beacon block.
28    pub data: SignedBeaconBlock<T>,
29}
30
31/// A signed beacon block.
32///
33/// The [`SignedBeaconBlock`](https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/phase0/beacon-chain.md#signedbeaconblock)
34/// object envelope from the CL spec.
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36pub struct SignedBeaconBlock<T = serde_json::Value> {
37    /// The beacon block message.
38    pub message: BeaconBlock<T>,
39    /// The BLS signature of the block.
40    pub signature: BlsSignature,
41}
42
43/// A beacon block.
44///
45/// The [`BeaconBlock`](https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/phase0/beacon-chain.md#beaconblock)
46/// object from the CL spec.
47#[serde_as]
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49pub struct BeaconBlock<T = serde_json::Value> {
50    /// The slot to which this block corresponds.
51    #[serde_as(as = "DisplayFromStr")]
52    pub slot: u64,
53    /// Index of validator in validator registry.
54    #[serde_as(as = "DisplayFromStr")]
55    pub proposer_index: u64,
56    /// The signing Merkle root of the parent `BeaconBlock`.
57    pub parent_root: B256,
58    /// The tree hash Merkle root of the `BeaconState` for the `BeaconBlock`.
59    pub state_root: B256,
60    /// The beacon block body.
61    pub body: T,
62}
63
64/// The Eth1Data object from the CL spec.
65///
66/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/phase0/beacon-chain.md#eth1data>
67#[serde_as]
68#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
69#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
70pub struct Eth1Data {
71    /// Root of the deposit tree.
72    pub deposit_root: B256,
73    /// Total number of deposits.
74    #[serde_as(as = "DisplayFromStr")]
75    pub deposit_count: u64,
76    /// Ethereum 1.x block hash.
77    pub block_hash: B256,
78}
79
80/// A checkpoint in the beacon chain.
81///
82/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/phase0/beacon-chain.md#checkpoint>
83#[serde_as]
84#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
85#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
86pub struct Checkpoint {
87    /// The epoch number.
88    #[serde_as(as = "DisplayFromStr")]
89    pub epoch: u64,
90    /// The root hash.
91    pub root: B256,
92}
93
94/// Attestation data.
95///
96/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/phase0/beacon-chain.md#attestationdata>
97#[serde_as]
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
99#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
100pub struct AttestationData {
101    /// The slot number.
102    #[serde_as(as = "DisplayFromStr")]
103    pub slot: u64,
104    /// The committee index.
105    #[serde_as(as = "DisplayFromStr")]
106    pub index: u64,
107    /// LMD GHOST vote - the beacon block root.
108    pub beacon_block_root: B256,
109    /// FFG source checkpoint.
110    pub source: Checkpoint,
111    /// FFG target checkpoint.
112    pub target: Checkpoint,
113}
114
115/// An attestation.
116///
117/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/phase0/beacon-chain.md#attestation>
118#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
119#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
120pub struct Attestation {
121    /// Attester aggregation bits.
122    pub aggregation_bits: Bytes,
123    /// The attestation data.
124    pub data: AttestationData,
125    /// BLS aggregate signature.
126    pub signature: BlsSignature,
127}
128
129/// An indexed attestation.
130///
131/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/phase0/beacon-chain.md#indexedattestation>
132#[serde_as]
133#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
134#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
135pub struct IndexedAttestation {
136    /// Attesting validator indices.
137    #[serde_as(as = "Vec<DisplayFromStr>")]
138    pub attesting_indices: Vec<u64>,
139    /// The attestation data.
140    pub data: AttestationData,
141    /// The BLS signature.
142    pub signature: BlsSignature,
143}
144
145/// A proposer slashing.
146///
147/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/phase0/beacon-chain.md#proposerslashing>
148#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
149#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
150pub struct ProposerSlashing {
151    /// First signed block header.
152    pub signed_header_1: SignedBeaconBlockHeader,
153    /// Second signed block header (conflicting).
154    pub signed_header_2: SignedBeaconBlockHeader,
155}
156
157/// A signed beacon block header.
158///
159/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/phase0/beacon-chain.md#signedbeaconblockheader>
160#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
161#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
162pub struct SignedBeaconBlockHeader {
163    /// The beacon block header.
164    pub message: BeaconBlockHeader,
165    /// The BLS signature.
166    pub signature: BlsSignature,
167}
168
169/// An attester slashing.
170///
171/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/phase0/beacon-chain.md#attesterslashing>
172#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
173#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
174pub struct AttesterSlashing {
175    /// First attestation.
176    pub attestation_1: IndexedAttestation,
177    /// Second attestation (conflicting).
178    pub attestation_2: IndexedAttestation,
179}
180
181/// Deposit data.
182///
183/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/phase0/beacon-chain.md#depositdata>
184#[serde_as]
185#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
186#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
187pub struct DepositData {
188    /// The validator's BLS public key.
189    pub pubkey: BlsPublicKey,
190    /// The withdrawal credentials.
191    pub withdrawal_credentials: B256,
192    /// Amount in Gwei.
193    #[serde_as(as = "DisplayFromStr")]
194    pub amount: u64,
195    /// Container self-signature.
196    pub signature: BlsSignature,
197}
198
199/// A deposit.
200///
201/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/phase0/beacon-chain.md#deposit>
202#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
203#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
204pub struct Deposit {
205    /// Branch in the deposit tree (proof).
206    pub proof: Vec<B256>,
207    /// The deposit data.
208    pub data: DepositData,
209}
210
211/// A voluntary exit message.
212///
213/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/phase0/beacon-chain.md#voluntaryexit>
214#[serde_as]
215#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
216#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
217pub struct VoluntaryExit {
218    /// Minimum epoch for processing exit.
219    #[serde_as(as = "DisplayFromStr")]
220    pub epoch: u64,
221    /// Index of the exiting validator.
222    #[serde_as(as = "DisplayFromStr")]
223    pub validator_index: u64,
224}
225
226/// A signed voluntary exit.
227///
228/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/phase0/beacon-chain.md#signedvoluntaryexit>
229#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
230#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
231pub struct SignedVoluntaryExit {
232    /// The voluntary exit message.
233    pub message: VoluntaryExit,
234    /// The BLS signature.
235    pub signature: BlsSignature,
236}
237
238/// Sync aggregate (Altair+).
239///
240/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/altair/beacon-chain.md#syncaggregate>
241#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
242#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
243pub struct SyncAggregate {
244    /// Aggregation bits of sync committee participation.
245    pub sync_committee_bits: Bytes,
246    /// BLS signature of the sync committee.
247    pub sync_committee_signature: BlsSignature,
248}
249
250/// BLS to execution change message (Capella+).
251///
252/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/capella/beacon-chain.md#blstoexecutionchange>
253#[serde_as]
254#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
255#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
256pub struct BlsToExecutionChange {
257    /// Validator index.
258    #[serde_as(as = "DisplayFromStr")]
259    pub validator_index: u64,
260    /// The BLS public key of the validator.
261    pub from_bls_pubkey: BlsPublicKey,
262    /// The execution address to change to.
263    pub to_execution_address: alloy_primitives::Address,
264}
265
266/// A signed BLS to execution change (Capella+).
267///
268/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/capella/beacon-chain.md#signedblstoexecutionchange>
269#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
270#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
271pub struct SignedBlsToExecutionChange {
272    /// The BLS to execution change message.
273    pub message: BlsToExecutionChange,
274    /// The BLS signature.
275    pub signature: BlsSignature,
276}
277
278/// The beacon block body for Phase0.
279///
280/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/phase0/beacon-chain.md#beaconblockbody>
281#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
282#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
283pub struct BeaconBlockBodyPhase0 {
284    /// The RANDAO reveal value provided by the validator.
285    pub randao_reveal: BlsSignature,
286    /// Eth1 data.
287    pub eth1_data: Eth1Data,
288    /// Graffiti (32 bytes).
289    pub graffiti: B256,
290    /// Proposer slashings.
291    pub proposer_slashings: Vec<ProposerSlashing>,
292    /// Attester slashings.
293    pub attester_slashings: Vec<AttesterSlashing>,
294    /// Attestations.
295    pub attestations: Vec<Attestation>,
296    /// Deposits.
297    pub deposits: Vec<Deposit>,
298    /// Voluntary exits.
299    pub voluntary_exits: Vec<SignedVoluntaryExit>,
300}
301
302/// The beacon block body for Altair.
303///
304/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/altair/beacon-chain.md#beaconblockbody>
305#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
306#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
307pub struct BeaconBlockBodyAltair {
308    /// The RANDAO reveal value provided by the validator.
309    pub randao_reveal: BlsSignature,
310    /// Eth1 data.
311    pub eth1_data: Eth1Data,
312    /// Graffiti (32 bytes).
313    pub graffiti: B256,
314    /// Proposer slashings.
315    pub proposer_slashings: Vec<ProposerSlashing>,
316    /// Attester slashings.
317    pub attester_slashings: Vec<AttesterSlashing>,
318    /// Attestations.
319    pub attestations: Vec<Attestation>,
320    /// Deposits.
321    pub deposits: Vec<Deposit>,
322    /// Voluntary exits.
323    pub voluntary_exits: Vec<SignedVoluntaryExit>,
324    /// Sync aggregate (new in Altair).
325    pub sync_aggregate: SyncAggregate,
326}
327
328/// The beacon block body for Bellatrix.
329///
330/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/bellatrix/beacon-chain.md#beaconblockbody>
331#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
332pub struct BeaconBlockBodyBellatrix<T = serde_json::Value> {
333    /// The RANDAO reveal value provided by the validator.
334    pub randao_reveal: BlsSignature,
335    /// Eth1 data.
336    pub eth1_data: Eth1Data,
337    /// Graffiti (32 bytes).
338    pub graffiti: B256,
339    /// Proposer slashings.
340    pub proposer_slashings: Vec<ProposerSlashing>,
341    /// Attester slashings.
342    pub attester_slashings: Vec<AttesterSlashing>,
343    /// Attestations.
344    pub attestations: Vec<Attestation>,
345    /// Deposits.
346    pub deposits: Vec<Deposit>,
347    /// Voluntary exits.
348    pub voluntary_exits: Vec<SignedVoluntaryExit>,
349    /// Sync aggregate.
350    pub sync_aggregate: SyncAggregate,
351    /// Execution payload (new in Bellatrix).
352    pub execution_payload: T,
353}
354
355/// The beacon block body for Capella.
356///
357/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/capella/beacon-chain.md#beaconblockbody>
358#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
359pub struct BeaconBlockBodyCapella<T = serde_json::Value> {
360    /// The RANDAO reveal value provided by the validator.
361    pub randao_reveal: BlsSignature,
362    /// Eth1 data.
363    pub eth1_data: Eth1Data,
364    /// Graffiti (32 bytes).
365    pub graffiti: B256,
366    /// Proposer slashings.
367    pub proposer_slashings: Vec<ProposerSlashing>,
368    /// Attester slashings.
369    pub attester_slashings: Vec<AttesterSlashing>,
370    /// Attestations.
371    pub attestations: Vec<Attestation>,
372    /// Deposits.
373    pub deposits: Vec<Deposit>,
374    /// Voluntary exits.
375    pub voluntary_exits: Vec<SignedVoluntaryExit>,
376    /// Sync aggregate.
377    pub sync_aggregate: SyncAggregate,
378    /// Execution payload.
379    pub execution_payload: T,
380    /// BLS to execution changes (new in Capella).
381    pub bls_to_execution_changes: Vec<SignedBlsToExecutionChange>,
382}
383
384/// The beacon block body for Deneb.
385///
386/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/deneb/beacon-chain.md#beaconblockbody>
387#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
388pub struct BeaconBlockBodyDeneb<T = serde_json::Value> {
389    /// The RANDAO reveal value provided by the validator.
390    pub randao_reveal: BlsSignature,
391    /// Eth1 data.
392    pub eth1_data: Eth1Data,
393    /// Graffiti (32 bytes).
394    pub graffiti: B256,
395    /// Proposer slashings.
396    pub proposer_slashings: Vec<ProposerSlashing>,
397    /// Attester slashings.
398    pub attester_slashings: Vec<AttesterSlashing>,
399    /// Attestations.
400    pub attestations: Vec<Attestation>,
401    /// Deposits.
402    pub deposits: Vec<Deposit>,
403    /// Voluntary exits.
404    pub voluntary_exits: Vec<SignedVoluntaryExit>,
405    /// Sync aggregate.
406    pub sync_aggregate: SyncAggregate,
407    /// Execution payload.
408    pub execution_payload: T,
409    /// BLS to execution changes.
410    pub bls_to_execution_changes: Vec<SignedBlsToExecutionChange>,
411    /// Blob KZG commitments (new in Deneb).
412    pub blob_kzg_commitments: Vec<Bytes>,
413}
414
415/// The beacon block body for Electra.
416///
417/// See <https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/electra/beacon-chain.md#beaconblockbody>
418#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
419pub struct BeaconBlockBodyElectra<T = serde_json::Value> {
420    /// The RANDAO reveal value provided by the validator.
421    pub randao_reveal: BlsSignature,
422    /// Eth1 data.
423    pub eth1_data: Eth1Data,
424    /// Graffiti (32 bytes).
425    pub graffiti: B256,
426    /// Proposer slashings.
427    pub proposer_slashings: Vec<ProposerSlashing>,
428    /// Attester slashings (Electra uses a different format).
429    pub attester_slashings: Vec<AttesterSlashing>,
430    /// Attestations.
431    pub attestations: Vec<Attestation>,
432    /// Deposits.
433    pub deposits: Vec<Deposit>,
434    /// Voluntary exits.
435    pub voluntary_exits: Vec<SignedVoluntaryExit>,
436    /// Sync aggregate.
437    pub sync_aggregate: SyncAggregate,
438    /// Execution payload.
439    pub execution_payload: T,
440    /// BLS to execution changes.
441    pub bls_to_execution_changes: Vec<SignedBlsToExecutionChange>,
442    /// Blob KZG commitments.
443    pub blob_kzg_commitments: Vec<Bytes>,
444    /// Execution requests (new in Electra).
445    #[serde(default, skip_serializing_if = "Option::is_none")]
446    pub execution_requests: Option<serde_json::Value>,
447}
448
449/// Type aliases for convenience.
450pub type SignedBeaconBlockPhase0 = SignedBeaconBlock<BeaconBlockBodyPhase0>;
451/// Type alias for Altair signed beacon block.
452pub type SignedBeaconBlockAltair = SignedBeaconBlock<BeaconBlockBodyAltair>;
453/// Type alias for Bellatrix signed beacon block.
454pub type SignedBeaconBlockBellatrix<T = serde_json::Value> =
455    SignedBeaconBlock<BeaconBlockBodyBellatrix<T>>;
456/// Type alias for Capella signed beacon block.
457pub type SignedBeaconBlockCapella<T = serde_json::Value> =
458    SignedBeaconBlock<BeaconBlockBodyCapella<T>>;
459/// Type alias for Deneb signed beacon block.
460pub type SignedBeaconBlockDeneb<T = serde_json::Value> = SignedBeaconBlock<BeaconBlockBodyDeneb<T>>;
461/// Type alias for Electra signed beacon block.
462pub type SignedBeaconBlockElectra<T = serde_json::Value> =
463    SignedBeaconBlock<BeaconBlockBodyElectra<T>>;
464
465#[cfg(test)]
466mod tests {
467    use super::*;
468
469    #[test]
470    fn serde_block_response_phase0() {
471        let s = r#"{
472            "version": "phase0",
473            "execution_optimistic": false,
474            "finalized": true,
475            "data": {
476                "message": {
477                    "slot": "1",
478                    "proposer_index": "1",
479                    "parent_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
480                    "state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
481                    "body": {
482                        "randao_reveal": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
483                        "eth1_data": {
484                            "deposit_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
485                            "deposit_count": "1",
486                            "block_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
487                        },
488                        "graffiti": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
489                        "proposer_slashings": [],
490                        "attester_slashings": [],
491                        "attestations": [],
492                        "deposits": [],
493                        "voluntary_exits": []
494                    }
495                },
496                "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
497            }
498        }"#;
499        let resp: BlockResponse<BeaconBlockBodyPhase0> = serde_json::from_str(s).unwrap();
500        assert_eq!(resp.version, "phase0");
501        assert!(resp.finalized);
502        assert_eq!(resp.data.message.slot, 1);
503    }
504
505    #[test]
506    fn serde_block_response_altair() {
507        let s = r#"{
508            "version": "altair",
509            "execution_optimistic": false,
510            "finalized": true,
511            "data": {
512                "message": {
513                    "slot": "100",
514                    "proposer_index": "42",
515                    "parent_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
516                    "state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
517                    "body": {
518                        "randao_reveal": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
519                        "eth1_data": {
520                            "deposit_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
521                            "deposit_count": "100",
522                            "block_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
523                        },
524                        "graffiti": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
525                        "proposer_slashings": [],
526                        "attester_slashings": [],
527                        "attestations": [],
528                        "deposits": [],
529                        "voluntary_exits": [],
530                        "sync_aggregate": {
531                            "sync_committee_bits": "0x01",
532                            "sync_committee_signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
533                        }
534                    }
535                },
536                "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
537            }
538        }"#;
539        let resp: BlockResponse<BeaconBlockBodyAltair> = serde_json::from_str(s).unwrap();
540        assert_eq!(resp.version, "altair");
541        assert_eq!(resp.data.message.slot, 100);
542        assert_eq!(resp.data.message.proposer_index, 42);
543    }
544
545    #[test]
546    fn serde_signed_beacon_block_generic() {
547        let s = r#"{
548            "message": {
549                "slot": "12225729",
550                "proposer_index": "496520",
551                "parent_root": "0x462f4abf9b6881724e6489085b3bb3931312e31ffb43f7cec3d0ee624dc2b58e",
552                "state_root": "0x2c6e3ff0b0f7bc33b30a020e75e69c2bba26fb42a7e234e8275e655170925a71",
553                "body": {
554                    "randao_reveal": "0x825dc181628713b55f40ed3f489be0c60f0513f88eecb25c7aa512ad24b912b3929bdf1930b50af4c18fb8b5f490352218a1c25adc01f7c3aaa50f982d762f589b4f5b6806e1d37e3f70af7afe990d1b1e8e337ac67b53bb7896f2052ecfccc1",
555                    "eth1_data": {
556                        "deposit_root": "0x2ebc563cabdbbacbc56f0de1d2d1c2d5315a4b071fcd8566aabbf0a45161c64e",
557                        "deposit_count": "2045305",
558                        "block_hash": "0x0958d83550263ff0d9f9a0bc5ea3cd2a136e0933b6f43cbb17f36e4da8d809b1"
559                    },
560                    "graffiti": "0x52502d4e502076312e31372e3000000000000000000000000000000000000000",
561                    "proposer_slashings": [],
562                    "attester_slashings": [],
563                    "attestations": [],
564                    "deposits": [],
565                    "voluntary_exits": [],
566                    "sync_aggregate": {
567                        "sync_committee_bits": "0x71b7f7596e64ef7f7ef4f938e9f68abfbfe95bff09393315bb93bbec7f7ef27effa4c7f25ba7cbdb87efbbf73fdaebb9efefeb3ef7fff8effafdd7aff5677bfc",
568                        "sync_committee_signature": "0xb45afdccf46b3518c295407594d82fcfd7fbff767f1b7bb2e7c9bdc8a0229232d201247b449d4bddf01fc974ce0b57601987fb401bb346062e53981cfb81dd6f9c519d645248a46ceba695c2d9630cfc68b26efc35f6ca14c49af9170581ad90"
569                    },
570                    "execution_payload": {}
571                }
572            },
573            "signature": "0x8a9cfe747dbb5d6ee1538638b2adfc304c8bcbeb03f489756ca7dc7a12081df892f38b924d19c9f5530c746b86a34beb019070bb7707de5a8efc8bdab8ca5668d7bb0e31c5ffd24913d23c80a6f6f70ba89e280dd46d19d6128ac7f42ffee93e"
574        }"#;
575        let block: SignedBeaconBlock = serde_json::from_str(s).unwrap();
576        assert_eq!(block.message.slot, 12225729);
577        assert_eq!(block.message.proposer_index, 496520);
578    }
579
580    #[test]
581    fn serde_eth1_data() {
582        let s = r#"{
583            "deposit_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
584            "deposit_count": "1",
585            "block_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
586        }"#;
587        let eth1_data: Eth1Data = serde_json::from_str(s).unwrap();
588        assert_eq!(eth1_data.deposit_count, 1);
589    }
590
591    #[test]
592    fn serde_attestation_data() {
593        let s = r#"{
594            "slot": "1",
595            "index": "1",
596            "beacon_block_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
597            "source": {
598                "epoch": "1",
599                "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
600            },
601            "target": {
602                "epoch": "1",
603                "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
604            }
605        }"#;
606        let data: AttestationData = serde_json::from_str(s).unwrap();
607        assert_eq!(data.slot, 1);
608        assert_eq!(data.index, 1);
609    }
610
611    #[test]
612    fn serde_voluntary_exit() {
613        let s = r#"{
614            "message": {
615                "epoch": "1",
616                "validator_index": "1"
617            },
618            "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
619        }"#;
620        let exit: SignedVoluntaryExit = serde_json::from_str(s).unwrap();
621        assert_eq!(exit.message.epoch, 1);
622        assert_eq!(exit.message.validator_index, 1);
623    }
624
625    #[test]
626    fn serde_sync_aggregate() {
627        let s = r#"{
628            "sync_committee_bits": "0x01",
629            "sync_committee_signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
630        }"#;
631        let _aggregate: SyncAggregate = serde_json::from_str(s).unwrap();
632    }
633
634    #[test]
635    fn serde_bls_to_execution_change() {
636        let s = r#"{
637            "message": {
638                "validator_index": "1",
639                "from_bls_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
640                "to_execution_address": "0xabcf8e0d4e9587369b2301d0790347320302cc09"
641            },
642            "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
643        }"#;
644        let change: SignedBlsToExecutionChange = serde_json::from_str(s).unwrap();
645        assert_eq!(change.message.validator_index, 1);
646    }
647
648    #[test]
649    fn serde_proposer_slashing() {
650        let s = r#"{
651            "signed_header_1": {
652                "message": {
653                    "slot": "1",
654                    "proposer_index": "1",
655                    "parent_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
656                    "state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
657                    "body_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
658                },
659                "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
660            },
661            "signed_header_2": {
662                "message": {
663                    "slot": "1",
664                    "proposer_index": "1",
665                    "parent_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
666                    "state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
667                    "body_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
668                },
669                "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
670            }
671        }"#;
672        let slashing: ProposerSlashing = serde_json::from_str(s).unwrap();
673        assert_eq!(slashing.signed_header_1.message.slot, 1);
674    }
675
676    #[cfg(feature = "ssz")]
677    mod ssz_tests {
678        use super::*;
679        use ssz::{Decode, Encode};
680
681        #[test]
682        fn ssz_roundtrip_eth1_data() {
683            let eth1_data = Eth1Data {
684                deposit_root: B256::repeat_byte(0x11),
685                deposit_count: 42,
686                block_hash: B256::repeat_byte(0x22),
687            };
688            let encoded = eth1_data.as_ssz_bytes();
689            let decoded = Eth1Data::from_ssz_bytes(&encoded).unwrap();
690            assert_eq!(eth1_data, decoded);
691        }
692
693        #[test]
694        fn ssz_roundtrip_checkpoint() {
695            let checkpoint = Checkpoint { epoch: 100, root: B256::repeat_byte(0x33) };
696            let encoded = checkpoint.as_ssz_bytes();
697            let decoded = Checkpoint::from_ssz_bytes(&encoded).unwrap();
698            assert_eq!(checkpoint, decoded);
699        }
700
701        #[test]
702        fn ssz_roundtrip_attestation_data() {
703            let data = AttestationData {
704                slot: 1000,
705                index: 5,
706                beacon_block_root: B256::repeat_byte(0x44),
707                source: Checkpoint { epoch: 10, root: B256::repeat_byte(0x55) },
708                target: Checkpoint { epoch: 11, root: B256::repeat_byte(0x66) },
709            };
710            let encoded = data.as_ssz_bytes();
711            let decoded = AttestationData::from_ssz_bytes(&encoded).unwrap();
712            assert_eq!(data, decoded);
713        }
714
715        #[test]
716        fn ssz_roundtrip_voluntary_exit() {
717            let exit = VoluntaryExit { epoch: 50, validator_index: 123 };
718            let encoded = exit.as_ssz_bytes();
719            let decoded = VoluntaryExit::from_ssz_bytes(&encoded).unwrap();
720            assert_eq!(exit, decoded);
721        }
722
723        #[test]
724        fn ssz_roundtrip_signed_voluntary_exit() {
725            use crate::BlsSignature;
726            let exit = SignedVoluntaryExit {
727                message: VoluntaryExit { epoch: 50, validator_index: 123 },
728                signature: BlsSignature::repeat_byte(0x77),
729            };
730            let encoded = exit.as_ssz_bytes();
731            let decoded = SignedVoluntaryExit::from_ssz_bytes(&encoded).unwrap();
732            assert_eq!(exit, decoded);
733        }
734
735        #[test]
736        fn ssz_roundtrip_deposit_data() {
737            use crate::BlsPublicKey;
738            let data = DepositData {
739                pubkey: BlsPublicKey::repeat_byte(0x88),
740                withdrawal_credentials: B256::repeat_byte(0x99),
741                amount: 32_000_000_000,
742                signature: crate::BlsSignature::repeat_byte(0xaa),
743            };
744            let encoded = data.as_ssz_bytes();
745            let decoded = DepositData::from_ssz_bytes(&encoded).unwrap();
746            assert_eq!(data, decoded);
747        }
748
749        #[test]
750        fn ssz_roundtrip_bls_to_execution_change() {
751            use crate::BlsPublicKey;
752            let change = BlsToExecutionChange {
753                validator_index: 456,
754                from_bls_pubkey: BlsPublicKey::repeat_byte(0xbb),
755                to_execution_address: alloy_primitives::Address::repeat_byte(0xcc),
756            };
757            let encoded = change.as_ssz_bytes();
758            let decoded = BlsToExecutionChange::from_ssz_bytes(&encoded).unwrap();
759            assert_eq!(change, decoded);
760        }
761
762        #[test]
763        fn ssz_roundtrip_signed_bls_to_execution_change() {
764            use crate::{BlsPublicKey, BlsSignature};
765            let change = SignedBlsToExecutionChange {
766                message: BlsToExecutionChange {
767                    validator_index: 789,
768                    from_bls_pubkey: BlsPublicKey::repeat_byte(0xdd),
769                    to_execution_address: alloy_primitives::Address::repeat_byte(0xee),
770                },
771                signature: BlsSignature::repeat_byte(0xff),
772            };
773            let encoded = change.as_ssz_bytes();
774            let decoded = SignedBlsToExecutionChange::from_ssz_bytes(&encoded).unwrap();
775            assert_eq!(change, decoded);
776        }
777
778        #[test]
779        fn ssz_roundtrip_sync_aggregate() {
780            let aggregate = SyncAggregate {
781                sync_committee_bits: Bytes::from_static(&[0x01, 0x02, 0x03]),
782                sync_committee_signature: crate::BlsSignature::repeat_byte(0x11),
783            };
784            let encoded = aggregate.as_ssz_bytes();
785            let decoded = SyncAggregate::from_ssz_bytes(&encoded).unwrap();
786            assert_eq!(aggregate, decoded);
787        }
788
789        #[test]
790        fn ssz_roundtrip_beacon_block_body_phase0() {
791            let body = BeaconBlockBodyPhase0 {
792                randao_reveal: crate::BlsSignature::repeat_byte(0x22),
793                eth1_data: Eth1Data {
794                    deposit_root: B256::repeat_byte(0x33),
795                    deposit_count: 100,
796                    block_hash: B256::repeat_byte(0x44),
797                },
798                graffiti: B256::repeat_byte(0x55),
799                proposer_slashings: vec![],
800                attester_slashings: vec![],
801                attestations: vec![],
802                deposits: vec![],
803                voluntary_exits: vec![],
804            };
805            let encoded = body.as_ssz_bytes();
806            let decoded = BeaconBlockBodyPhase0::from_ssz_bytes(&encoded).unwrap();
807            assert_eq!(body, decoded);
808        }
809
810        #[test]
811        fn ssz_roundtrip_beacon_block_body_altair() {
812            let body = BeaconBlockBodyAltair {
813                randao_reveal: crate::BlsSignature::repeat_byte(0x66),
814                eth1_data: Eth1Data {
815                    deposit_root: B256::repeat_byte(0x77),
816                    deposit_count: 200,
817                    block_hash: B256::repeat_byte(0x88),
818                },
819                graffiti: B256::repeat_byte(0x99),
820                proposer_slashings: vec![],
821                attester_slashings: vec![],
822                attestations: vec![],
823                deposits: vec![],
824                voluntary_exits: vec![],
825                sync_aggregate: SyncAggregate {
826                    sync_committee_bits: Bytes::from_static(&[0xaa]),
827                    sync_committee_signature: crate::BlsSignature::repeat_byte(0xbb),
828                },
829            };
830            let encoded = body.as_ssz_bytes();
831            let decoded = BeaconBlockBodyAltair::from_ssz_bytes(&encoded).unwrap();
832            assert_eq!(body, decoded);
833        }
834    }
835}