use super::*; use alloy_rlp::RlpEncodable; use serde::{Deserialize, Serialize}; use ssz::{Decode, TryFromIter}; use ssz_types::{FixedVector, VariableList, typenum::Unsigned}; use strum::EnumString; use superstruct::superstruct; use types::beacon_block_body::KzgCommitments; use types::blob_sidecar::BlobsList; use types::execution_requests::{ ConsolidationRequests, DepositRequests, RequestType, WithdrawalRequests, }; use types::{Blob, KzgProof}; #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct JsonRequestBody<'a> { pub jsonrpc: &'a str, pub method: &'a str, pub params: serde_json::Value, pub id: serde_json::Value, } #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct JsonError { pub code: i64, pub message: String, } #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct JsonResponseBody { pub jsonrpc: String, #[serde(default)] pub error: Option, #[serde(default)] pub result: serde_json::Value, pub id: serde_json::Value, } #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[serde(transparent)] pub struct TransparentJsonPayloadId(#[serde(with = "serde_utils::bytes_8_hex")] pub PayloadId); impl From for TransparentJsonPayloadId { fn from(id: PayloadId) -> Self { Self(id) } } impl From for PayloadId { fn from(wrapper: TransparentJsonPayloadId) -> Self { wrapper.0 } } /// On the request, use a transparent wrapper. pub type JsonPayloadIdRequest = TransparentJsonPayloadId; /// On the response, expect without the object wrapper (non-transparent). #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct JsonPayloadIdResponse { #[serde(with = "serde_utils::bytes_8_hex")] pub payload_id: PayloadId, } #[superstruct( variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), variant_attributes( derive(Debug, PartialEq, Default, Serialize, Deserialize,), serde(bound = "E: EthSpec", rename_all = "camelCase"), ), cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"), partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant") )] #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(bound = "E: EthSpec", rename_all = "camelCase", untagged)] pub struct JsonExecutionPayload { pub parent_hash: ExecutionBlockHash, #[serde(with = "serde_utils::address_hex")] pub fee_recipient: Address, pub state_root: Hash256, pub receipts_root: Hash256, #[serde(with = "serde_logs_bloom")] pub logs_bloom: FixedVector, pub prev_randao: Hash256, #[serde(with = "serde_utils::u64_hex_be")] pub block_number: u64, #[serde(with = "serde_utils::u64_hex_be")] pub gas_limit: u64, #[serde(with = "serde_utils::u64_hex_be")] pub gas_used: u64, #[serde(with = "serde_utils::u64_hex_be")] pub timestamp: u64, #[serde(with = "ssz_types::serde_utils::hex_var_list")] pub extra_data: VariableList, #[serde(with = "serde_utils::u256_hex_be")] pub base_fee_per_gas: Uint256, pub block_hash: ExecutionBlockHash, #[serde(with = "ssz_types::serde_utils::list_of_hex_var_list")] pub transactions: Transactions, #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas))] pub withdrawals: VariableList, #[superstruct(only(Deneb, Electra, Fulu, Gloas))] #[serde(with = "serde_utils::u64_hex_be")] pub blob_gas_used: u64, #[superstruct(only(Deneb, Electra, Fulu, Gloas))] #[serde(with = "serde_utils::u64_hex_be")] pub excess_blob_gas: u64, } impl From> for JsonExecutionPayloadBellatrix { fn from(payload: ExecutionPayloadBellatrix) -> Self { JsonExecutionPayloadBellatrix { parent_hash: payload.parent_hash, fee_recipient: payload.fee_recipient, state_root: payload.state_root, receipts_root: payload.receipts_root, logs_bloom: payload.logs_bloom, prev_randao: payload.prev_randao, block_number: payload.block_number, gas_limit: payload.gas_limit, gas_used: payload.gas_used, timestamp: payload.timestamp, extra_data: payload.extra_data, base_fee_per_gas: payload.base_fee_per_gas, block_hash: payload.block_hash, transactions: payload.transactions, } } } impl TryFrom> for JsonExecutionPayloadCapella { type Error = ssz_types::Error; fn try_from(payload: ExecutionPayloadCapella) -> Result { Ok(JsonExecutionPayloadCapella { parent_hash: payload.parent_hash, fee_recipient: payload.fee_recipient, state_root: payload.state_root, receipts_root: payload.receipts_root, logs_bloom: payload.logs_bloom, prev_randao: payload.prev_randao, block_number: payload.block_number, gas_limit: payload.gas_limit, gas_used: payload.gas_used, timestamp: payload.timestamp, extra_data: payload.extra_data, base_fee_per_gas: payload.base_fee_per_gas, block_hash: payload.block_hash, transactions: payload.transactions, withdrawals: withdrawals_to_json(payload.withdrawals)?, }) } } impl TryFrom> for JsonExecutionPayloadDeneb { type Error = ssz_types::Error; fn try_from(payload: ExecutionPayloadDeneb) -> Result { Ok(JsonExecutionPayloadDeneb { parent_hash: payload.parent_hash, fee_recipient: payload.fee_recipient, state_root: payload.state_root, receipts_root: payload.receipts_root, logs_bloom: payload.logs_bloom, prev_randao: payload.prev_randao, block_number: payload.block_number, gas_limit: payload.gas_limit, gas_used: payload.gas_used, timestamp: payload.timestamp, extra_data: payload.extra_data, base_fee_per_gas: payload.base_fee_per_gas, block_hash: payload.block_hash, transactions: payload.transactions, withdrawals: withdrawals_to_json(payload.withdrawals)?, blob_gas_used: payload.blob_gas_used, excess_blob_gas: payload.excess_blob_gas, }) } } impl TryFrom> for JsonExecutionPayloadElectra { type Error = ssz_types::Error; fn try_from(payload: ExecutionPayloadElectra) -> Result { Ok(JsonExecutionPayloadElectra { parent_hash: payload.parent_hash, fee_recipient: payload.fee_recipient, state_root: payload.state_root, receipts_root: payload.receipts_root, logs_bloom: payload.logs_bloom, prev_randao: payload.prev_randao, block_number: payload.block_number, gas_limit: payload.gas_limit, gas_used: payload.gas_used, timestamp: payload.timestamp, extra_data: payload.extra_data, base_fee_per_gas: payload.base_fee_per_gas, block_hash: payload.block_hash, transactions: payload.transactions, withdrawals: withdrawals_to_json(payload.withdrawals)?, blob_gas_used: payload.blob_gas_used, excess_blob_gas: payload.excess_blob_gas, }) } } impl TryFrom> for JsonExecutionPayloadFulu { type Error = ssz_types::Error; fn try_from(payload: ExecutionPayloadFulu) -> Result { Ok(JsonExecutionPayloadFulu { parent_hash: payload.parent_hash, fee_recipient: payload.fee_recipient, state_root: payload.state_root, receipts_root: payload.receipts_root, logs_bloom: payload.logs_bloom, prev_randao: payload.prev_randao, block_number: payload.block_number, gas_limit: payload.gas_limit, gas_used: payload.gas_used, timestamp: payload.timestamp, extra_data: payload.extra_data, base_fee_per_gas: payload.base_fee_per_gas, block_hash: payload.block_hash, transactions: payload.transactions, withdrawals: withdrawals_to_json(payload.withdrawals)?, blob_gas_used: payload.blob_gas_used, excess_blob_gas: payload.excess_blob_gas, }) } } impl TryFrom> for JsonExecutionPayloadGloas { type Error = ssz_types::Error; fn try_from(payload: ExecutionPayloadGloas) -> Result { Ok(JsonExecutionPayloadGloas { parent_hash: payload.parent_hash, fee_recipient: payload.fee_recipient, state_root: payload.state_root, receipts_root: payload.receipts_root, logs_bloom: payload.logs_bloom, prev_randao: payload.prev_randao, block_number: payload.block_number, gas_limit: payload.gas_limit, gas_used: payload.gas_used, timestamp: payload.timestamp, extra_data: payload.extra_data, base_fee_per_gas: payload.base_fee_per_gas, block_hash: payload.block_hash, transactions: payload.transactions, withdrawals: withdrawals_to_json(payload.withdrawals)?, blob_gas_used: payload.blob_gas_used, excess_blob_gas: payload.excess_blob_gas, }) } } impl TryFrom> for JsonExecutionPayload { type Error = ssz_types::Error; fn try_from(execution_payload: ExecutionPayload) -> Result { match execution_payload { ExecutionPayload::Bellatrix(payload) => { Ok(JsonExecutionPayload::Bellatrix(payload.into())) } ExecutionPayload::Capella(payload) => { Ok(JsonExecutionPayload::Capella(payload.try_into()?)) } ExecutionPayload::Deneb(payload) => { Ok(JsonExecutionPayload::Deneb(payload.try_into()?)) } ExecutionPayload::Electra(payload) => { Ok(JsonExecutionPayload::Electra(payload.try_into()?)) } ExecutionPayload::Fulu(payload) => Ok(JsonExecutionPayload::Fulu(payload.try_into()?)), ExecutionPayload::Gloas(payload) => { Ok(JsonExecutionPayload::Gloas(payload.try_into()?)) } } } } impl From> for ExecutionPayloadBellatrix { fn from(payload: JsonExecutionPayloadBellatrix) -> Self { ExecutionPayloadBellatrix { parent_hash: payload.parent_hash, fee_recipient: payload.fee_recipient, state_root: payload.state_root, receipts_root: payload.receipts_root, logs_bloom: payload.logs_bloom, prev_randao: payload.prev_randao, block_number: payload.block_number, gas_limit: payload.gas_limit, gas_used: payload.gas_used, timestamp: payload.timestamp, extra_data: payload.extra_data, base_fee_per_gas: payload.base_fee_per_gas, block_hash: payload.block_hash, transactions: payload.transactions, } } } impl TryFrom> for ExecutionPayloadCapella { type Error = ssz_types::Error; fn try_from(payload: JsonExecutionPayloadCapella) -> Result { Ok(ExecutionPayloadCapella { parent_hash: payload.parent_hash, fee_recipient: payload.fee_recipient, state_root: payload.state_root, receipts_root: payload.receipts_root, logs_bloom: payload.logs_bloom, prev_randao: payload.prev_randao, block_number: payload.block_number, gas_limit: payload.gas_limit, gas_used: payload.gas_used, timestamp: payload.timestamp, extra_data: payload.extra_data, base_fee_per_gas: payload.base_fee_per_gas, block_hash: payload.block_hash, transactions: payload.transactions, withdrawals: withdrawals_from_json(payload.withdrawals)?, }) } } impl TryFrom> for ExecutionPayloadDeneb { type Error = ssz_types::Error; fn try_from(payload: JsonExecutionPayloadDeneb) -> Result { Ok(ExecutionPayloadDeneb { parent_hash: payload.parent_hash, fee_recipient: payload.fee_recipient, state_root: payload.state_root, receipts_root: payload.receipts_root, logs_bloom: payload.logs_bloom, prev_randao: payload.prev_randao, block_number: payload.block_number, gas_limit: payload.gas_limit, gas_used: payload.gas_used, timestamp: payload.timestamp, extra_data: payload.extra_data, base_fee_per_gas: payload.base_fee_per_gas, block_hash: payload.block_hash, transactions: payload.transactions, withdrawals: withdrawals_from_json(payload.withdrawals)?, blob_gas_used: payload.blob_gas_used, excess_blob_gas: payload.excess_blob_gas, }) } } impl TryFrom> for ExecutionPayloadElectra { type Error = ssz_types::Error; fn try_from(payload: JsonExecutionPayloadElectra) -> Result { Ok(ExecutionPayloadElectra { parent_hash: payload.parent_hash, fee_recipient: payload.fee_recipient, state_root: payload.state_root, receipts_root: payload.receipts_root, logs_bloom: payload.logs_bloom, prev_randao: payload.prev_randao, block_number: payload.block_number, gas_limit: payload.gas_limit, gas_used: payload.gas_used, timestamp: payload.timestamp, extra_data: payload.extra_data, base_fee_per_gas: payload.base_fee_per_gas, block_hash: payload.block_hash, transactions: payload.transactions, withdrawals: withdrawals_from_json(payload.withdrawals)?, blob_gas_used: payload.blob_gas_used, excess_blob_gas: payload.excess_blob_gas, }) } } impl TryFrom> for ExecutionPayloadFulu { type Error = ssz_types::Error; fn try_from(payload: JsonExecutionPayloadFulu) -> Result { Ok(ExecutionPayloadFulu { parent_hash: payload.parent_hash, fee_recipient: payload.fee_recipient, state_root: payload.state_root, receipts_root: payload.receipts_root, logs_bloom: payload.logs_bloom, prev_randao: payload.prev_randao, block_number: payload.block_number, gas_limit: payload.gas_limit, gas_used: payload.gas_used, timestamp: payload.timestamp, extra_data: payload.extra_data, base_fee_per_gas: payload.base_fee_per_gas, block_hash: payload.block_hash, transactions: payload.transactions, withdrawals: withdrawals_from_json(payload.withdrawals)?, blob_gas_used: payload.blob_gas_used, excess_blob_gas: payload.excess_blob_gas, }) } } impl TryFrom> for ExecutionPayloadGloas { type Error = ssz_types::Error; fn try_from(payload: JsonExecutionPayloadGloas) -> Result { Ok(ExecutionPayloadGloas { parent_hash: payload.parent_hash, fee_recipient: payload.fee_recipient, state_root: payload.state_root, receipts_root: payload.receipts_root, logs_bloom: payload.logs_bloom, prev_randao: payload.prev_randao, block_number: payload.block_number, gas_limit: payload.gas_limit, gas_used: payload.gas_used, timestamp: payload.timestamp, extra_data: payload.extra_data, base_fee_per_gas: payload.base_fee_per_gas, block_hash: payload.block_hash, transactions: payload.transactions, withdrawals: withdrawals_from_json(payload.withdrawals)?, blob_gas_used: payload.blob_gas_used, excess_blob_gas: payload.excess_blob_gas, }) } } impl TryFrom> for ExecutionPayload { type Error = ssz_types::Error; fn try_from(json_execution_payload: JsonExecutionPayload) -> Result { match json_execution_payload { JsonExecutionPayload::Bellatrix(payload) => { Ok(ExecutionPayload::Bellatrix(payload.into())) } JsonExecutionPayload::Capella(payload) => { Ok(ExecutionPayload::Capella(payload.try_into()?)) } JsonExecutionPayload::Deneb(payload) => { Ok(ExecutionPayload::Deneb(payload.try_into()?)) } JsonExecutionPayload::Electra(payload) => { Ok(ExecutionPayload::Electra(payload.try_into()?)) } JsonExecutionPayload::Fulu(payload) => Ok(ExecutionPayload::Fulu(payload.try_into()?)), JsonExecutionPayload::Gloas(payload) => { Ok(ExecutionPayload::Gloas(payload.try_into()?)) } } } } #[derive(Debug, Clone)] pub enum RequestsError { InvalidHex(hex::FromHexError), EmptyRequest(usize), InvalidOrdering, InvalidPrefix(u8), DecodeError(String), } /// Format of `ExecutionRequests` received over the engine api. /// /// Array of ssz-encoded requests list encoded as hex bytes prefixed /// with a `RequestType` #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] #[serde(transparent)] pub struct JsonExecutionRequests(pub Vec); impl TryFrom for ExecutionRequests { type Error = RequestsError; fn try_from(value: JsonExecutionRequests) -> Result { let mut requests = ExecutionRequests::default(); let mut prev_prefix: Option = None; for (i, request) in value.0.into_iter().enumerate() { // hex string let decoded_bytes = hex::decode(request.strip_prefix("0x").unwrap_or(&request)) .map_err(RequestsError::InvalidHex)?; // The first byte of each element is the `request_type` and the remaining bytes are the `request_data`. // Elements with empty `request_data` **MUST** be excluded from the list. let Some((prefix_byte, request_bytes)) = decoded_bytes.split_first() else { return Err(RequestsError::EmptyRequest(i)); }; if request_bytes.is_empty() { return Err(RequestsError::EmptyRequest(i)); } // Elements of the list **MUST** be ordered by `request_type` in ascending order let current_prefix = RequestType::from_u8(*prefix_byte) .ok_or(RequestsError::InvalidPrefix(*prefix_byte))?; if let Some(prev) = prev_prefix && prev.to_u8() >= current_prefix.to_u8() { return Err(RequestsError::InvalidOrdering); } prev_prefix = Some(current_prefix); match current_prefix { RequestType::Deposit => { requests.deposits = DepositRequests::::from_ssz_bytes(request_bytes) .map_err(|e| { RequestsError::DecodeError(format!( "Failed to decode DepositRequest from EL: {:?}", e )) })?; } RequestType::Withdrawal => { requests.withdrawals = WithdrawalRequests::::from_ssz_bytes(request_bytes) .map_err(|e| { RequestsError::DecodeError(format!( "Failed to decode WithdrawalRequest from EL: {:?}", e )) })?; } RequestType::Consolidation => { requests.consolidations = ConsolidationRequests::::from_ssz_bytes(request_bytes).map_err(|e| { RequestsError::DecodeError(format!( "Failed to decode ConsolidationRequest from EL: {:?}", e )) })?; } } } Ok(requests) } } #[superstruct( variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), variant_attributes( derive(Debug, PartialEq, Serialize, Deserialize), serde(bound = "E: EthSpec", rename_all = "camelCase") ), cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"), partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant") )] #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub struct JsonGetPayloadResponse { #[superstruct( only(Bellatrix), partial_getter(rename = "execution_payload_bellatrix") )] pub execution_payload: JsonExecutionPayloadBellatrix, #[superstruct(only(Capella), partial_getter(rename = "execution_payload_capella"))] pub execution_payload: JsonExecutionPayloadCapella, #[superstruct(only(Deneb), partial_getter(rename = "execution_payload_deneb"))] pub execution_payload: JsonExecutionPayloadDeneb, #[superstruct(only(Electra), partial_getter(rename = "execution_payload_electra"))] pub execution_payload: JsonExecutionPayloadElectra, #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))] pub execution_payload: JsonExecutionPayloadFulu, #[superstruct(only(Gloas), partial_getter(rename = "execution_payload_gloas"))] pub execution_payload: JsonExecutionPayloadGloas, #[serde(with = "serde_utils::u256_hex_be")] pub block_value: Uint256, #[superstruct(only(Deneb, Electra, Fulu, Gloas))] pub blobs_bundle: JsonBlobsBundleV1, #[superstruct(only(Deneb, Electra, Fulu, Gloas))] pub should_override_builder: bool, #[superstruct(only(Electra, Fulu, Gloas))] pub execution_requests: JsonExecutionRequests, } impl TryFrom> for GetPayloadResponse { type Error = String; fn try_from(json_get_payload_response: JsonGetPayloadResponse) -> Result { match json_get_payload_response { JsonGetPayloadResponse::Bellatrix(response) => { Ok(GetPayloadResponse::Bellatrix(GetPayloadResponseBellatrix { execution_payload: response.execution_payload.into(), block_value: response.block_value, })) } JsonGetPayloadResponse::Capella(response) => { Ok(GetPayloadResponse::Capella(GetPayloadResponseCapella { execution_payload: response.execution_payload.try_into().map_err(|e| { format!("Failed to convert json to execution payload: {:?}", e) })?, block_value: response.block_value, })) } JsonGetPayloadResponse::Deneb(response) => { Ok(GetPayloadResponse::Deneb(GetPayloadResponseDeneb { execution_payload: response.execution_payload.try_into().map_err(|e| { format!("Failed to convert json to execution payload: {:?}", e) })?, block_value: response.block_value, blobs_bundle: response.blobs_bundle.into(), should_override_builder: response.should_override_builder, })) } JsonGetPayloadResponse::Electra(response) => { Ok(GetPayloadResponse::Electra(GetPayloadResponseElectra { execution_payload: response.execution_payload.try_into().map_err(|e| { format!("Failed to convert json to execution payload: {:?}", e) })?, block_value: response.block_value, blobs_bundle: response.blobs_bundle.into(), should_override_builder: response.should_override_builder, requests: response.execution_requests.try_into().map_err(|e| { format!("Failed to convert json to execution requests: {:?}", e) })?, })) } JsonGetPayloadResponse::Fulu(response) => { Ok(GetPayloadResponse::Fulu(GetPayloadResponseFulu { execution_payload: response.execution_payload.try_into().map_err(|e| { format!("Failed to convert json to execution payload: {:?}", e) })?, block_value: response.block_value, blobs_bundle: response.blobs_bundle.into(), should_override_builder: response.should_override_builder, requests: response.execution_requests.try_into().map_err(|e| { format!("Failed to convert json to execution requests: {:?}", e) })?, })) } JsonGetPayloadResponse::Gloas(response) => { Ok(GetPayloadResponse::Gloas(GetPayloadResponseGloas { execution_payload: response.execution_payload.try_into().map_err(|e| { format!("Failed to convert json to execution payload: {:?}", e) })?, block_value: response.block_value, blobs_bundle: response.blobs_bundle.into(), should_override_builder: response.should_override_builder, requests: response.execution_requests.try_into().map_err(|e| { format!("Failed to convert json to execution requests: {:?}", e) })?, })) } } } } #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct JsonWithdrawal { #[serde(with = "serde_utils::u64_hex_be")] pub index: u64, #[serde(with = "serde_utils::u64_hex_be")] pub validator_index: u64, #[serde(with = "serde_utils::address_hex")] pub address: Address, #[serde(with = "serde_utils::u64_hex_be")] pub amount: u64, } impl From for JsonWithdrawal { fn from(withdrawal: Withdrawal) -> Self { Self { index: withdrawal.index, validator_index: withdrawal.validator_index, address: withdrawal.address, amount: withdrawal.amount, } } } impl From for Withdrawal { fn from(jw: JsonWithdrawal) -> Self { Self { index: jw.index, validator_index: jw.validator_index, address: jw.address, amount: jw.amount, } } } // Helper functions to convert between `VariableList` and `VariableList`. fn withdrawals_to_json( list: VariableList, ) -> Result, ssz_types::Error> where N: Unsigned, { VariableList::try_from_iter(list.into_iter().map(Into::into)) } fn withdrawals_from_json( list: VariableList, ) -> Result, ssz_types::Error> where N: Unsigned, { VariableList::try_from_iter(list.into_iter().map(Into::into)) } #[derive(Debug, PartialEq, Clone, RlpEncodable)] pub struct EncodableJsonWithdrawal<'a> { pub index: u64, pub validator_index: u64, pub address: &'a [u8], pub amount: u64, } impl<'a> From<&'a JsonWithdrawal> for EncodableJsonWithdrawal<'a> { fn from(json_withdrawal: &'a JsonWithdrawal) -> Self { Self { index: json_withdrawal.index, validator_index: json_withdrawal.validator_index, address: json_withdrawal.address.as_slice(), amount: json_withdrawal.amount, } } } #[superstruct( variants(V1, V2, V3), variant_attributes( derive(Debug, Clone, PartialEq, Serialize, Deserialize), serde(rename_all = "camelCase") ), cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"), partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant") )] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub struct JsonPayloadAttributes { #[serde(with = "serde_utils::u64_hex_be")] pub timestamp: u64, pub prev_randao: Hash256, #[serde(with = "serde_utils::address_hex")] pub suggested_fee_recipient: Address, #[superstruct(only(V2, V3))] pub withdrawals: Vec, #[superstruct(only(V3))] pub parent_beacon_block_root: Hash256, } impl From for JsonPayloadAttributes { fn from(payload_attributes: PayloadAttributes) -> Self { match payload_attributes { PayloadAttributes::V1(pa) => Self::V1(JsonPayloadAttributesV1 { timestamp: pa.timestamp, prev_randao: pa.prev_randao, suggested_fee_recipient: pa.suggested_fee_recipient, }), PayloadAttributes::V2(pa) => Self::V2(JsonPayloadAttributesV2 { timestamp: pa.timestamp, prev_randao: pa.prev_randao, suggested_fee_recipient: pa.suggested_fee_recipient, withdrawals: pa.withdrawals.into_iter().map(Into::into).collect(), }), PayloadAttributes::V3(pa) => Self::V3(JsonPayloadAttributesV3 { timestamp: pa.timestamp, prev_randao: pa.prev_randao, suggested_fee_recipient: pa.suggested_fee_recipient, withdrawals: pa.withdrawals.into_iter().map(Into::into).collect(), parent_beacon_block_root: pa.parent_beacon_block_root, }), } } } impl From for PayloadAttributes { fn from(json_payload_attributes: JsonPayloadAttributes) -> Self { match json_payload_attributes { JsonPayloadAttributes::V1(jpa) => Self::V1(PayloadAttributesV1 { timestamp: jpa.timestamp, prev_randao: jpa.prev_randao, suggested_fee_recipient: jpa.suggested_fee_recipient, }), JsonPayloadAttributes::V2(jpa) => Self::V2(PayloadAttributesV2 { timestamp: jpa.timestamp, prev_randao: jpa.prev_randao, suggested_fee_recipient: jpa.suggested_fee_recipient, withdrawals: jpa.withdrawals.into_iter().map(Into::into).collect(), }), JsonPayloadAttributes::V3(jpa) => Self::V3(PayloadAttributesV3 { timestamp: jpa.timestamp, prev_randao: jpa.prev_randao, suggested_fee_recipient: jpa.suggested_fee_recipient, withdrawals: jpa.withdrawals.into_iter().map(Into::into).collect(), parent_beacon_block_root: jpa.parent_beacon_block_root, }), } } } #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(bound = "E: EthSpec", rename_all = "camelCase")] pub struct JsonBlobsBundleV1 { pub commitments: KzgCommitments, pub proofs: KzgProofs, #[serde(with = "ssz_types::serde_utils::list_of_hex_fixed_vec")] pub blobs: BlobsList, } impl From> for JsonBlobsBundleV1 { fn from(blobs_bundle: BlobsBundle) -> Self { Self { commitments: blobs_bundle.commitments, proofs: blobs_bundle.proofs, blobs: blobs_bundle.blobs, } } } impl From> for BlobsBundle { fn from(json_blobs_bundle: JsonBlobsBundleV1) -> Self { Self { commitments: json_blobs_bundle.commitments, proofs: json_blobs_bundle.proofs, blobs: json_blobs_bundle.blobs, } } } #[superstruct( variants(V1, V2), variant_attributes( derive(Debug, Clone, PartialEq, Serialize, Deserialize), serde(bound = "E: EthSpec", rename_all = "camelCase") ) )] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct BlobAndProof { #[serde(with = "ssz_types::serde_utils::hex_fixed_vec")] pub blob: Blob, /// KZG proof for the blob (Deneb) #[superstruct(only(V1))] pub proof: KzgProof, /// KZG cell proofs for the extended blob (PeerDAS) #[superstruct(only(V2))] pub proofs: KzgProofs, } #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct JsonForkchoiceStateV1 { pub head_block_hash: ExecutionBlockHash, pub safe_block_hash: ExecutionBlockHash, pub finalized_block_hash: ExecutionBlockHash, } impl From for JsonForkchoiceStateV1 { fn from(f: ForkchoiceState) -> Self { // Use this verbose deconstruction pattern to ensure no field is left unused. let ForkchoiceState { head_block_hash, safe_block_hash, finalized_block_hash, } = f; Self { head_block_hash, safe_block_hash, finalized_block_hash, } } } impl From for ForkchoiceState { fn from(j: JsonForkchoiceStateV1) -> Self { // Use this verbose deconstruction pattern to ensure no field is left unused. let JsonForkchoiceStateV1 { head_block_hash, safe_block_hash, finalized_block_hash, } = j; Self { head_block_hash, safe_block_hash, finalized_block_hash, } } } #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, EnumString)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[strum(serialize_all = "SCREAMING_SNAKE_CASE")] pub enum JsonPayloadStatusV1Status { Valid, Invalid, Syncing, Accepted, InvalidBlockHash, } #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct JsonPayloadStatusV1 { pub status: JsonPayloadStatusV1Status, pub latest_valid_hash: Option, pub validation_error: Option, } impl From for JsonPayloadStatusV1Status { fn from(e: PayloadStatusV1Status) -> Self { match e { PayloadStatusV1Status::Valid => JsonPayloadStatusV1Status::Valid, PayloadStatusV1Status::Invalid => JsonPayloadStatusV1Status::Invalid, PayloadStatusV1Status::Syncing => JsonPayloadStatusV1Status::Syncing, PayloadStatusV1Status::Accepted => JsonPayloadStatusV1Status::Accepted, PayloadStatusV1Status::InvalidBlockHash => JsonPayloadStatusV1Status::InvalidBlockHash, } } } impl From for PayloadStatusV1Status { fn from(j: JsonPayloadStatusV1Status) -> Self { match j { JsonPayloadStatusV1Status::Valid => PayloadStatusV1Status::Valid, JsonPayloadStatusV1Status::Invalid => PayloadStatusV1Status::Invalid, JsonPayloadStatusV1Status::Syncing => PayloadStatusV1Status::Syncing, JsonPayloadStatusV1Status::Accepted => PayloadStatusV1Status::Accepted, JsonPayloadStatusV1Status::InvalidBlockHash => PayloadStatusV1Status::InvalidBlockHash, } } } impl From for JsonPayloadStatusV1 { fn from(p: PayloadStatusV1) -> Self { // Use this verbose deconstruction pattern to ensure no field is left unused. let PayloadStatusV1 { status, latest_valid_hash, validation_error, } = p; Self { status: status.into(), latest_valid_hash, validation_error, } } } impl From for PayloadStatusV1 { fn from(j: JsonPayloadStatusV1) -> Self { // Use this verbose deconstruction pattern to ensure no field is left unused. let JsonPayloadStatusV1 { status, latest_valid_hash, validation_error, } = j; Self { status: status.into(), latest_valid_hash, validation_error, } } } #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct JsonForkchoiceUpdatedV1Response { pub payload_status: JsonPayloadStatusV1, pub payload_id: Option, } impl From for ForkchoiceUpdatedResponse { fn from(j: JsonForkchoiceUpdatedV1Response) -> Self { // Use this verbose deconstruction pattern to ensure no field is left unused. let JsonForkchoiceUpdatedV1Response { payload_status: status, payload_id, } = j; Self { payload_status: status.into(), payload_id: payload_id.map(Into::into), } } } impl From for JsonForkchoiceUpdatedV1Response { fn from(f: ForkchoiceUpdatedResponse) -> Self { // Use this verbose deconstruction pattern to ensure no field is left unused. let ForkchoiceUpdatedResponse { payload_status: status, payload_id, } = f; Self { payload_status: status.into(), payload_id: payload_id.map(Into::into), } } } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(bound = "E: EthSpec")] pub struct JsonExecutionPayloadBodyV1 { #[serde(with = "ssz_types::serde_utils::list_of_hex_var_list")] pub transactions: Transactions, pub withdrawals: Option>, } impl TryFrom> for ExecutionPayloadBodyV1 { type Error = ssz_types::Error; fn try_from(value: JsonExecutionPayloadBodyV1) -> Result { Ok(Self { transactions: value.transactions, withdrawals: value.withdrawals.map(withdrawals_from_json).transpose()?, }) } } impl TryFrom> for JsonExecutionPayloadBodyV1 { type Error = ssz_types::Error; fn try_from(value: ExecutionPayloadBodyV1) -> Result { Ok(Self { transactions: value.transactions, withdrawals: value.withdrawals.map(withdrawals_to_json).transpose()?, }) } } #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TransitionConfigurationV1 { #[serde(with = "serde_utils::u256_hex_be")] pub terminal_total_difficulty: Uint256, pub terminal_block_hash: ExecutionBlockHash, #[serde(with = "serde_utils::u64_hex_be")] pub terminal_block_number: u64, } /// Serializes the `logs_bloom` field of an `ExecutionPayload`. pub mod serde_logs_bloom { use super::*; use serde::{Deserializer, Serializer}; use serde_utils::hex::PrefixedHexVisitor; pub fn serialize(bytes: &FixedVector, serializer: S) -> Result where S: Serializer, U: Unsigned, { let mut hex_string: String = "0x".to_string(); hex_string.push_str(&hex::encode(&bytes[..])); serializer.serialize_str(&hex_string) } pub fn deserialize<'de, D, U>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, U: Unsigned, { let vec = deserializer.deserialize_string(PrefixedHexVisitor)?; FixedVector::new(vec) .map_err(|e| serde::de::Error::custom(format!("invalid logs bloom: {:?}", e))) } } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct JsonClientVersionV1 { pub code: String, // This `default` is required until Geth v1.13.x is no longer supported on mainnet. // See: https://github.com/ethereum/go-ethereum/pull/29351 #[serde(default)] pub name: String, pub version: String, pub commit: String, } impl From for JsonClientVersionV1 { fn from(client_version: ClientVersionV1) -> Self { Self { code: client_version.code.to_string(), name: client_version.name, version: client_version.version, commit: client_version.commit.to_string(), } } } impl TryFrom for ClientVersionV1 { type Error = String; fn try_from(json: JsonClientVersionV1) -> Result { Ok(Self { code: json.code.try_into()?, name: json.name, version: json.version, commit: json.commit.try_into()?, }) } } #[cfg(test)] mod tests { use ssz::Encode; use types::{ ConsolidationRequest, DepositRequest, MainnetEthSpec, PublicKeyBytes, RequestType, SignatureBytes, WithdrawalRequest, }; use super::*; fn create_request_string(prefix: u8, request_bytes: &T) -> String { format!( "0x{:02x}{}", prefix, hex::encode(request_bytes.as_ssz_bytes()) ) } /// Tests all error conditions except ssz decoding errors /// /// *** /// Elements of the list MUST be ordered by request_type in ascending order. /// Elements with empty request_data MUST be excluded from the list. /// If any element is out of order, has a length of 1-byte or shorter, /// or more than one element has the same type byte, client software MUST return -32602: Invalid params error. /// *** #[test] fn test_invalid_execution_requests() { let deposit_request = DepositRequest { pubkey: PublicKeyBytes::empty(), withdrawal_credentials: Hash256::random(), amount: 32, signature: SignatureBytes::empty(), index: 0, }; let consolidation_request = ConsolidationRequest { source_address: Address::random(), source_pubkey: PublicKeyBytes::empty(), target_pubkey: PublicKeyBytes::empty(), }; let withdrawal_request = WithdrawalRequest { amount: 32, source_address: Address::random(), validator_pubkey: PublicKeyBytes::empty(), }; // First check a valid request with all requests assert!( ExecutionRequests::::try_from(JsonExecutionRequests(vec![ create_request_string(RequestType::Deposit.to_u8(), &deposit_request), create_request_string(RequestType::Withdrawal.to_u8(), &withdrawal_request), create_request_string(RequestType::Consolidation.to_u8(), &consolidation_request), ])) .is_ok() ); // Single requests assert!( ExecutionRequests::::try_from(JsonExecutionRequests(vec![ create_request_string(RequestType::Deposit.to_u8(), &deposit_request), ])) .is_ok() ); assert!( ExecutionRequests::::try_from(JsonExecutionRequests(vec![ create_request_string(RequestType::Withdrawal.to_u8(), &withdrawal_request), ])) .is_ok() ); assert!( ExecutionRequests::::try_from(JsonExecutionRequests(vec![ create_request_string(RequestType::Consolidation.to_u8(), &consolidation_request), ])) .is_ok() ); // Out of order assert!(matches!( ExecutionRequests::::try_from(JsonExecutionRequests(vec![ create_request_string(RequestType::Withdrawal.to_u8(), &withdrawal_request), create_request_string(RequestType::Deposit.to_u8(), &deposit_request), ])) .unwrap_err(), RequestsError::InvalidOrdering )); assert!(matches!( ExecutionRequests::::try_from(JsonExecutionRequests(vec![ create_request_string(RequestType::Consolidation.to_u8(), &consolidation_request), create_request_string(RequestType::Withdrawal.to_u8(), &withdrawal_request), ])) .unwrap_err(), RequestsError::InvalidOrdering )); assert!(matches!( ExecutionRequests::::try_from(JsonExecutionRequests(vec![ create_request_string(RequestType::Consolidation.to_u8(), &consolidation_request), create_request_string(RequestType::Deposit.to_u8(), &deposit_request), ])) .unwrap_err(), RequestsError::InvalidOrdering )); // Multiple requests of same type assert!(matches!( ExecutionRequests::::try_from(JsonExecutionRequests(vec![ create_request_string(RequestType::Deposit.to_u8(), &deposit_request), create_request_string(RequestType::Deposit.to_u8(), &deposit_request), ])) .unwrap_err(), RequestsError::InvalidOrdering )); // Invalid prefix assert!(matches!( ExecutionRequests::::try_from(JsonExecutionRequests(vec![ create_request_string(42, &deposit_request), ])) .unwrap_err(), RequestsError::InvalidPrefix(42) )); // Prefix followed by no data assert!(matches!( ExecutionRequests::::try_from(JsonExecutionRequests(vec![ create_request_string(RequestType::Deposit.to_u8(), &deposit_request), create_request_string( RequestType::Consolidation.to_u8(), &Vec::::new() ), ])) .unwrap_err(), RequestsError::EmptyRequest(1) )); // Empty request assert!(matches!( ExecutionRequests::::try_from(JsonExecutionRequests(vec![ create_request_string(RequestType::Deposit.to_u8(), &deposit_request), "0x".to_string() ])) .unwrap_err(), RequestsError::EmptyRequest(1) )); } }