diff --git a/crates/rpc/rpc-types/src/beacon/constants.rs b/crates/rpc/rpc-types/src/beacon/constants.rs new file mode 100644 index 000000000..917a30724 --- /dev/null +++ b/crates/rpc/rpc-types/src/beacon/constants.rs @@ -0,0 +1,4 @@ +pub const BLS_DST: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; +pub const BLS_PUBLIC_KEY_BYTES_LEN: usize = 48; +pub const BLS_SECRET_KEY_BYTES_LEN: usize = 32; +pub const BLS_SIGNATURE_BYTES_LEN: usize = 96; diff --git a/crates/rpc/rpc-types/src/eth/engine/beacon_api/events/attestation.rs b/crates/rpc/rpc-types/src/beacon/events/attestation.rs similarity index 100% rename from crates/rpc/rpc-types/src/eth/engine/beacon_api/events/attestation.rs rename to crates/rpc/rpc-types/src/beacon/events/attestation.rs diff --git a/crates/rpc/rpc-types/src/eth/engine/beacon_api/events/light_client_finality.rs b/crates/rpc/rpc-types/src/beacon/events/light_client_finality.rs similarity index 100% rename from crates/rpc/rpc-types/src/eth/engine/beacon_api/events/light_client_finality.rs rename to crates/rpc/rpc-types/src/beacon/events/light_client_finality.rs diff --git a/crates/rpc/rpc-types/src/eth/engine/beacon_api/events/light_client_optimistic.rs b/crates/rpc/rpc-types/src/beacon/events/light_client_optimistic.rs similarity index 100% rename from crates/rpc/rpc-types/src/eth/engine/beacon_api/events/light_client_optimistic.rs rename to crates/rpc/rpc-types/src/beacon/events/light_client_optimistic.rs diff --git a/crates/rpc/rpc-types/src/eth/engine/beacon_api/events/mod.rs b/crates/rpc/rpc-types/src/beacon/events/mod.rs similarity index 99% rename from crates/rpc/rpc-types/src/eth/engine/beacon_api/events/mod.rs rename to crates/rpc/rpc-types/src/beacon/events/mod.rs index d4f1bc8f4..501494a91 100644 --- a/crates/rpc/rpc-types/src/eth/engine/beacon_api/events/mod.rs +++ b/crates/rpc/rpc-types/src/beacon/events/mod.rs @@ -270,7 +270,7 @@ pub struct PayloadAttributesData { /// /// Note: this uses the beacon API format which uses snake-case and quoted decimals rather than /// big-endian hex. - #[serde(with = "crate::eth::engine::payload::beacon_api_payload_attributes")] + #[serde(with = "crate::beacon::payload::beacon_api_payload_attributes")] pub payload_attributes: PayloadAttributes, } diff --git a/crates/rpc/rpc-types/src/beacon/mod.rs b/crates/rpc/rpc-types/src/beacon/mod.rs new file mode 100644 index 000000000..7264a9036 --- /dev/null +++ b/crates/rpc/rpc-types/src/beacon/mod.rs @@ -0,0 +1,17 @@ +#![allow(missing_docs)] +//! Types for the Ethereum 2.0 RPC protocol (beacon chain) + +use alloy_primitives::FixedBytes; +use constants::{BLS_PUBLIC_KEY_BYTES_LEN, BLS_SIGNATURE_BYTES_LEN}; + +pub mod constants; +/// Beacon API events support. +pub mod events; +pub mod payload; +pub mod withdrawals; + +/// BLS signature type +pub type BlsSignature = FixedBytes; + +/// BLS public key type +pub type BlsPublicKey = FixedBytes; diff --git a/crates/rpc/rpc-types/src/beacon/payload.rs b/crates/rpc/rpc-types/src/beacon/payload.rs new file mode 100644 index 000000000..7a2610c40 --- /dev/null +++ b/crates/rpc/rpc-types/src/beacon/payload.rs @@ -0,0 +1,460 @@ +#![allow(missing_docs)] +//! Payload support for the beacon API. +//! +//! Internal helper module to deserialize/serialize the payload attributes for the beacon API, which +//! uses snake case and quoted decimals. +//! +//! This is necessary because we don't want to allow a mixture of both formats, hence `serde` +//! aliases are not an option. +//! +//! See also + +use crate::{ + beacon::withdrawals::BeaconWithdrawal, engine::ExecutionPayloadV3, ExecutionPayload, + ExecutionPayloadV1, ExecutionPayloadV2, Withdrawal, +}; +use alloy_primitives::{Address, Bloom, Bytes, B256, U256, U64}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde_with::{serde_as, DeserializeAs, DisplayFromStr, SerializeAs}; +use std::borrow::Cow; + +#[serde_as] +#[derive(Serialize, Deserialize)] +struct BeaconPayloadAttributes { + #[serde_as(as = "DisplayFromStr")] + timestamp: u64, + prev_randao: B256, + suggested_fee_recipient: Address, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(as = "Option>")] + withdrawals: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + parent_beacon_block_root: Option, + #[cfg(feature = "optimism")] + #[serde(flatten)] + optimism_payload_attributes: BeaconOptimismPayloadAttributes, +} + +/// Optimism Payload Attributes +#[cfg(feature = "optimism")] +#[serde_as] +#[derive(Serialize, Deserialize)] +struct BeaconOptimismPayloadAttributes { + #[serde(default, skip_serializing_if = "Option::is_none")] + transactions: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + no_tx_pool: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde_as(as = "Option")] + gas_limit: Option, +} + +/// A helper module for serializing and deserializing the payload attributes for the beacon API. +/// +/// The beacon API encoded object has equivalent fields to the +/// [PayloadAttributes](crate::engine::PayloadAttributes) with two differences: +/// 1) `snake_case` identifiers must be used rather than `camelCase`; +/// 2) integers must be encoded as quoted decimals rather than big-endian hex. +pub mod beacon_api_payload_attributes { + use super::*; + use crate::engine::PayloadAttributes; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + /// Serialize the payload attributes for the beacon API. + pub fn serialize( + payload_attributes: &PayloadAttributes, + serializer: S, + ) -> Result + where + S: Serializer, + { + let beacon_api_payload_attributes = BeaconPayloadAttributes { + timestamp: payload_attributes.timestamp.to(), + prev_randao: payload_attributes.prev_randao, + suggested_fee_recipient: payload_attributes.suggested_fee_recipient, + withdrawals: payload_attributes.withdrawals.clone(), + parent_beacon_block_root: payload_attributes.parent_beacon_block_root, + #[cfg(feature = "optimism")] + optimism_payload_attributes: BeaconOptimismPayloadAttributes { + transactions: payload_attributes.optimism_payload_attributes.transactions.clone(), + no_tx_pool: payload_attributes.optimism_payload_attributes.no_tx_pool, + gas_limit: payload_attributes.optimism_payload_attributes.gas_limit, + }, + }; + beacon_api_payload_attributes.serialize(serializer) + } + + /// Deserialize the payload attributes for the beacon API. + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let beacon_api_payload_attributes = BeaconPayloadAttributes::deserialize(deserializer)?; + Ok(PayloadAttributes { + timestamp: U64::from(beacon_api_payload_attributes.timestamp), + prev_randao: beacon_api_payload_attributes.prev_randao, + suggested_fee_recipient: beacon_api_payload_attributes.suggested_fee_recipient, + withdrawals: beacon_api_payload_attributes.withdrawals, + parent_beacon_block_root: beacon_api_payload_attributes.parent_beacon_block_root, + #[cfg(feature = "optimism")] + optimism_payload_attributes: crate::eth::engine::OptimismPayloadAttributes { + transactions: beacon_api_payload_attributes + .optimism_payload_attributes + .transactions, + no_tx_pool: beacon_api_payload_attributes.optimism_payload_attributes.no_tx_pool, + gas_limit: beacon_api_payload_attributes.optimism_payload_attributes.gas_limit, + }, + }) + } +} + +#[serde_as] +#[derive(Debug, Serialize, Deserialize)] +struct BeaconExecutionPayloadV1<'a> { + parent_hash: Cow<'a, B256>, + fee_recipient: Cow<'a, Address>, + state_root: Cow<'a, B256>, + receipts_root: Cow<'a, B256>, + logs_bloom: Cow<'a, Bloom>, + prev_randao: Cow<'a, B256>, + #[serde_as(as = "DisplayFromStr")] + block_number: u64, + #[serde_as(as = "DisplayFromStr")] + gas_limit: u64, + #[serde_as(as = "DisplayFromStr")] + gas_used: u64, + #[serde_as(as = "DisplayFromStr")] + timestamp: u64, + extra_data: Cow<'a, Bytes>, + #[serde_as(as = "DisplayFromStr")] + base_fee_per_gas: U256, + block_hash: Cow<'a, B256>, + transactions: Cow<'a, Vec>, +} + +impl<'a> From> for ExecutionPayloadV1 { + fn from(payload: BeaconExecutionPayloadV1<'a>) -> Self { + let BeaconExecutionPayloadV1 { + parent_hash, + fee_recipient, + state_root, + receipts_root, + logs_bloom, + prev_randao, + block_number, + gas_limit, + gas_used, + timestamp, + extra_data, + base_fee_per_gas, + block_hash, + transactions, + } = payload; + ExecutionPayloadV1 { + parent_hash: parent_hash.into_owned(), + fee_recipient: fee_recipient.into_owned(), + state_root: state_root.into_owned(), + receipts_root: receipts_root.into_owned(), + logs_bloom: logs_bloom.into_owned(), + prev_randao: prev_randao.into_owned(), + block_number: U64::from(block_number), + gas_limit: U64::from(gas_limit), + gas_used: U64::from(gas_used), + timestamp: U64::from(timestamp), + extra_data: extra_data.into_owned(), + base_fee_per_gas, + block_hash: block_hash.into_owned(), + transactions: transactions.into_owned(), + } + } +} + +impl<'a> From<&'a ExecutionPayloadV1> for BeaconExecutionPayloadV1<'a> { + fn from(value: &'a ExecutionPayloadV1) -> Self { + let ExecutionPayloadV1 { + parent_hash, + fee_recipient, + state_root, + receipts_root, + logs_bloom, + prev_randao, + block_number, + gas_limit, + gas_used, + timestamp, + extra_data, + base_fee_per_gas, + block_hash, + transactions, + } = value; + + BeaconExecutionPayloadV1 { + parent_hash: Cow::Borrowed(parent_hash), + fee_recipient: Cow::Borrowed(fee_recipient), + state_root: Cow::Borrowed(state_root), + receipts_root: Cow::Borrowed(receipts_root), + logs_bloom: Cow::Borrowed(logs_bloom), + prev_randao: Cow::Borrowed(prev_randao), + block_number: block_number.to(), + gas_limit: gas_limit.to(), + gas_used: gas_used.to(), + timestamp: timestamp.to(), + extra_data: Cow::Borrowed(extra_data), + base_fee_per_gas: *base_fee_per_gas, + block_hash: Cow::Borrowed(block_hash), + transactions: Cow::Borrowed(transactions), + } + } +} + +/// A helper serde module to convert from/to the Beacon API which uses quoted decimals rather than +/// big-endian hex. +pub mod beacon_payload_v1 { + use super::*; + use serde::{Deserializer, Serializer}; + + /// Serialize the payload attributes for the beacon API. + pub fn serialize( + payload_attributes: &ExecutionPayloadV1, + serializer: S, + ) -> Result + where + S: Serializer, + { + BeaconExecutionPayloadV1::from(payload_attributes).serialize(serializer) + } + + /// Deserialize the payload attributes for the beacon API. + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + BeaconExecutionPayloadV1::deserialize(deserializer).map(Into::into) + } +} + +#[serde_as] +#[derive(Debug, Serialize, Deserialize)] +struct BeaconExecutionPayloadV2<'a> { + /// Inner V1 payload + #[serde(flatten)] + payload_inner: BeaconExecutionPayloadV1<'a>, + /// Array of [`Withdrawal`] enabled with V2 + /// See + #[serde_as(as = "Vec")] + withdrawals: Vec, +} + +impl<'a> From> for ExecutionPayloadV2 { + fn from(payload: BeaconExecutionPayloadV2<'a>) -> Self { + let BeaconExecutionPayloadV2 { payload_inner, withdrawals } = payload; + ExecutionPayloadV2 { payload_inner: payload_inner.into(), withdrawals } + } +} + +impl<'a> From<&'a ExecutionPayloadV2> for BeaconExecutionPayloadV2<'a> { + fn from(value: &'a ExecutionPayloadV2) -> Self { + let ExecutionPayloadV2 { payload_inner, withdrawals } = value; + BeaconExecutionPayloadV2 { + payload_inner: payload_inner.into(), + withdrawals: withdrawals.clone(), + } + } +} + +/// A helper serde module to convert from/to the Beacon API which uses quoted decimals rather than +/// big-endian hex. +pub mod beacon_payload_v2 { + use super::*; + use serde::{Deserializer, Serializer}; + + /// Serialize the payload attributes for the beacon API. + pub fn serialize( + payload_attributes: &ExecutionPayloadV2, + serializer: S, + ) -> Result + where + S: Serializer, + { + BeaconExecutionPayloadV2::from(payload_attributes).serialize(serializer) + } + + /// Deserialize the payload attributes for the beacon API. + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + BeaconExecutionPayloadV2::deserialize(deserializer).map(Into::into) + } +} + +#[serde_as] +#[derive(Debug, Serialize, Deserialize)] +struct BeaconExecutionPayloadV3<'a> { + /// Inner V1 payload + #[serde(flatten)] + payload_inner: BeaconExecutionPayloadV2<'a>, + /// Array of [`U64`] representing blob gas used, enabled with V3 + /// See + #[serde_as(as = "DisplayFromStr")] + blob_gas_used: u64, + /// Array of [`U64`] representing excess blob gas, enabled with V3 + /// See + #[serde_as(as = "DisplayFromStr")] + excess_blob_gas: u64, +} + +impl<'a> From> for ExecutionPayloadV3 { + fn from(payload: BeaconExecutionPayloadV3<'a>) -> Self { + let BeaconExecutionPayloadV3 { payload_inner, blob_gas_used, excess_blob_gas } = payload; + ExecutionPayloadV3 { + payload_inner: payload_inner.into(), + blob_gas_used: U64::from(blob_gas_used), + excess_blob_gas: U64::from(excess_blob_gas), + } + } +} + +impl<'a> From<&'a ExecutionPayloadV3> for BeaconExecutionPayloadV3<'a> { + fn from(value: &'a ExecutionPayloadV3) -> Self { + let ExecutionPayloadV3 { payload_inner, blob_gas_used, excess_blob_gas } = value; + BeaconExecutionPayloadV3 { + payload_inner: payload_inner.into(), + blob_gas_used: blob_gas_used.to(), + excess_blob_gas: excess_blob_gas.to(), + } + } +} + +/// A helper serde module to convert from/to the Beacon API which uses quoted decimals rather than +/// big-endian hex. +pub mod beacon_payload_v3 { + use super::*; + use serde::{Deserializer, Serializer}; + + /// Serialize the payload attributes for the beacon API. + pub fn serialize( + payload_attributes: &ExecutionPayloadV3, + serializer: S, + ) -> Result + where + S: Serializer, + { + BeaconExecutionPayloadV3::from(payload_attributes).serialize(serializer) + } + + /// Deserialize the payload attributes for the beacon API. + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + BeaconExecutionPayloadV3::deserialize(deserializer).map(Into::into) + } +} + +/// Represents all possible payload versions. +#[derive(Debug, Serialize)] +#[serde(untagged)] +enum BeaconExecutionPayload<'a> { + /// V1 payload + V1(BeaconExecutionPayloadV1<'a>), + /// V2 payload + V2(BeaconExecutionPayloadV2<'a>), + /// V3 payload + V3(BeaconExecutionPayloadV3<'a>), +} + +// Deserializes untagged ExecutionPayload by trying each variant in falling order +impl<'de> Deserialize<'de> for BeaconExecutionPayload<'de> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum BeaconExecutionPayloadDesc<'a> { + V3(BeaconExecutionPayloadV3<'a>), + V2(BeaconExecutionPayloadV2<'a>), + V1(BeaconExecutionPayloadV1<'a>), + } + match BeaconExecutionPayloadDesc::deserialize(deserializer)? { + BeaconExecutionPayloadDesc::V3(payload) => Ok(Self::V3(payload)), + BeaconExecutionPayloadDesc::V2(payload) => Ok(Self::V2(payload)), + BeaconExecutionPayloadDesc::V1(payload) => Ok(Self::V1(payload)), + } + } +} + +impl<'a> From> for ExecutionPayload { + fn from(payload: BeaconExecutionPayload<'a>) -> Self { + match payload { + BeaconExecutionPayload::V1(payload) => { + ExecutionPayload::V1(ExecutionPayloadV1::from(payload)) + } + BeaconExecutionPayload::V2(payload) => { + ExecutionPayload::V2(ExecutionPayloadV2::from(payload)) + } + BeaconExecutionPayload::V3(payload) => { + ExecutionPayload::V3(ExecutionPayloadV3::from(payload)) + } + } + } +} + +impl<'a> From<&'a ExecutionPayload> for BeaconExecutionPayload<'a> { + fn from(value: &'a ExecutionPayload) -> Self { + match value { + ExecutionPayload::V1(payload) => { + BeaconExecutionPayload::V1(BeaconExecutionPayloadV1::from(payload)) + } + ExecutionPayload::V2(payload) => { + BeaconExecutionPayload::V2(BeaconExecutionPayloadV2::from(payload)) + } + ExecutionPayload::V3(payload) => { + BeaconExecutionPayload::V3(BeaconExecutionPayloadV3::from(payload)) + } + } + } +} + +impl<'a> SerializeAs for BeaconExecutionPayload<'a> { + fn serialize_as(source: &ExecutionPayload, serializer: S) -> Result + where + S: Serializer, + { + beacon_payload::serialize(source, serializer) + } +} + +impl<'de> DeserializeAs<'de, ExecutionPayload> for BeaconExecutionPayload<'de> { + fn deserialize_as(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + beacon_payload::deserialize(deserializer) + } +} + +pub mod beacon_payload { + use super::*; + use serde::{Deserializer, Serializer}; + + /// Serialize the payload attributes for the beacon API. + pub fn serialize( + payload_attributes: &ExecutionPayload, + serializer: S, + ) -> Result + where + S: Serializer, + { + BeaconExecutionPayload::from(payload_attributes).serialize(serializer) + } + + /// Deserialize the payload attributes for the beacon API. + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + BeaconExecutionPayload::deserialize(deserializer).map(Into::into) + } +} diff --git a/crates/rpc/rpc-types/src/beacon/withdrawals.rs b/crates/rpc/rpc-types/src/beacon/withdrawals.rs new file mode 100644 index 000000000..6a9282588 --- /dev/null +++ b/crates/rpc/rpc-types/src/beacon/withdrawals.rs @@ -0,0 +1,72 @@ +use alloy_primitives::Address; + +use crate::Withdrawal; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde_with::{serde_as, DeserializeAs, DisplayFromStr, SerializeAs}; + +/// Same as [Withdrawal] but respects the Beacon API format which uses snake-case and quoted +/// decimals. +#[serde_as] +#[derive(Serialize, Deserialize, Clone)] +pub(crate) struct BeaconWithdrawal { + #[serde_as(as = "DisplayFromStr")] + index: u64, + #[serde_as(as = "DisplayFromStr")] + validator_index: u64, + address: Address, + #[serde_as(as = "DisplayFromStr")] + amount: u64, +} + +impl SerializeAs for BeaconWithdrawal { + fn serialize_as(source: &Withdrawal, serializer: S) -> Result + where + S: Serializer, + { + beacon_withdrawals::serialize(source, serializer) + } +} + +impl<'de> DeserializeAs<'de, Withdrawal> for BeaconWithdrawal { + fn deserialize_as(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + beacon_withdrawals::deserialize(deserializer) + } +} + +/// A helper serde module to convert from/to the Beacon API which uses quoted decimals rather than +/// big-endian hex. +pub mod beacon_withdrawals { + use super::*; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + /// Serialize the payload attributes for the beacon API. + pub fn serialize(payload_attributes: &Withdrawal, serializer: S) -> Result + where + S: Serializer, + { + let withdrawal = BeaconWithdrawal { + index: payload_attributes.index, + validator_index: payload_attributes.validator_index, + address: payload_attributes.address, + amount: payload_attributes.amount, + }; + withdrawal.serialize(serializer) + } + + /// Deserialize the payload attributes for the beacon API. + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let withdrawal = BeaconWithdrawal::deserialize(deserializer)?; + Ok(Withdrawal { + index: withdrawal.index, + validator_index: withdrawal.validator_index, + address: withdrawal.address, + amount: withdrawal.amount, + }) + } +} diff --git a/crates/rpc/rpc-types/src/eth/engine/beacon_api/mod.rs b/crates/rpc/rpc-types/src/eth/engine/beacon_api/mod.rs deleted file mode 100644 index 0a3764a21..000000000 --- a/crates/rpc/rpc-types/src/eth/engine/beacon_api/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -/// Beacon API events support. -pub mod events; diff --git a/crates/rpc/rpc-types/src/eth/engine/mod.rs b/crates/rpc/rpc-types/src/eth/engine/mod.rs index ead7e56a7..334adf056 100644 --- a/crates/rpc/rpc-types/src/eth/engine/mod.rs +++ b/crates/rpc/rpc-types/src/eth/engine/mod.rs @@ -1,6 +1,5 @@ -//! Engine API types: and following the execution specs - #![allow(missing_docs)] +//! Engine API types: and following the execution specs mod cancun; mod forkchoice; @@ -8,9 +7,6 @@ pub mod payload; mod transition; pub use self::{cancun::*, forkchoice::*, payload::*, transition::*}; -/// Beacon API types -pub mod beacon_api; - /// The list of all supported Engine capabilities available over the engine endpoint. pub const CAPABILITIES: [&str; 12] = [ "engine_forkchoiceUpdatedV1", diff --git a/crates/rpc/rpc-types/src/eth/engine/payload.rs b/crates/rpc/rpc-types/src/eth/engine/payload.rs index a64de3a3b..5259e41b6 100644 --- a/crates/rpc/rpc-types/src/eth/engine/payload.rs +++ b/crates/rpc/rpc-types/src/eth/engine/payload.rs @@ -1,9 +1,8 @@ -use crate::eth::{transaction::BlobTransactionSidecar, withdrawal::BeaconAPIWithdrawal}; +use crate::eth::transaction::BlobTransactionSidecar; pub use crate::Withdrawal; use alloy_primitives::{Address, Bloom, Bytes, B256, B64, U256, U64}; use c_kzg::{Blob, Bytes48}; -use serde::{ser::SerializeMap, Deserialize, Serialize, Serializer}; -use serde_with::{serde_as, DisplayFromStr}; +use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; /// The execution payload body response that allows for `null` values. pub type ExecutionPayloadBodiesV1 = Vec>; @@ -229,7 +228,8 @@ impl BlobsBundleV1 { /// An execution payload, which can be either [ExecutionPayloadV1], [ExecutionPayloadV2], or /// [ExecutionPayloadV3]. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[serde(untagged)] pub enum ExecutionPayload { /// V1 payload V1(ExecutionPayloadV1), @@ -304,6 +304,27 @@ impl From for ExecutionPayload { } } +// Deserializes untagged ExecutionPayload by trying each variant in falling order +impl<'de> Deserialize<'de> for ExecutionPayload { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum ExecutionPayloadDesc { + V3(ExecutionPayloadV3), + V2(ExecutionPayloadV2), + V1(ExecutionPayloadV1), + } + match ExecutionPayloadDesc::deserialize(deserializer)? { + ExecutionPayloadDesc::V3(payload) => Ok(Self::V3(payload)), + ExecutionPayloadDesc::V2(payload) => Ok(Self::V2(payload)), + ExecutionPayloadDesc::V1(payload) => Ok(Self::V1(payload)), + } + } +} + /// Error that can occur when handling payloads. #[derive(thiserror::Error, Debug)] pub enum PayloadError { @@ -405,71 +426,6 @@ pub struct OptimismPayloadAttributes { pub gas_limit: Option, } -#[serde_as] -#[derive(Serialize, Deserialize)] -struct BeaconAPIPayloadAttributes { - #[serde_as(as = "DisplayFromStr")] - timestamp: u64, - prev_randao: B256, - suggested_fee_recipient: Address, - #[serde(skip_serializing_if = "Option::is_none")] - #[serde_as(as = "Option>")] - withdrawals: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - parent_beacon_block_root: Option, - #[cfg(feature = "optimism")] - #[serde(flatten)] - optimism_payload_attributes: OptimismPayloadAttributes, -} - -/// A helper module for serializing and deserializing the payload attributes for the beacon API. -/// -/// The beacon API encoded object has equivalent fields to the [PayloadAttributes] with two -/// differences: -/// 1) `snake_case` identifiers must be used rather than `camelCase`; -/// 2) integers must be encoded as quoted decimals rather than big-endian hex. -pub mod beacon_api_payload_attributes { - use super::*; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - - /// Serialize the payload attributes for the beacon API. - pub fn serialize( - payload_attributes: &PayloadAttributes, - serializer: S, - ) -> Result - where - S: Serializer, - { - let beacon_api_payload_attributes = BeaconAPIPayloadAttributes { - timestamp: payload_attributes.timestamp.to(), - prev_randao: payload_attributes.prev_randao, - suggested_fee_recipient: payload_attributes.suggested_fee_recipient, - withdrawals: payload_attributes.withdrawals.clone(), - parent_beacon_block_root: payload_attributes.parent_beacon_block_root, - #[cfg(feature = "optimism")] - optimism_payload_attributes: payload_attributes.optimism_payload_attributes.clone(), - }; - beacon_api_payload_attributes.serialize(serializer) - } - - /// Deserialize the payload attributes for the beacon API. - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let beacon_api_payload_attributes = BeaconAPIPayloadAttributes::deserialize(deserializer)?; - Ok(PayloadAttributes { - timestamp: U64::from(beacon_api_payload_attributes.timestamp), - prev_randao: beacon_api_payload_attributes.prev_randao, - suggested_fee_recipient: beacon_api_payload_attributes.suggested_fee_recipient, - withdrawals: beacon_api_payload_attributes.withdrawals, - parent_beacon_block_root: beacon_api_payload_attributes.parent_beacon_block_root, - #[cfg(feature = "optimism")] - optimism_payload_attributes: beacon_api_payload_attributes.optimism_payload_attributes, - }) - } -} - /// This structure contains the result of processing a payload or fork choice update. #[derive(Clone, Debug, PartialEq, Eq, Deserialize)] #[serde(rename_all = "camelCase")] @@ -715,6 +671,9 @@ mod tests { let s = r#"{"parentHash":"0x67ead97eb79b47a1638659942384143f36ed44275d4182799875ab5a87324055","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x4e3c608a9f2e129fccb91a1dae7472e78013b8e654bccc8d224ce3d63ae17006","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0x44bb4b98c59dbb726f96ffceb5ee028dcbe35b9bba4f9ffd56aeebf8d1e4db62","blockNumber":"0x1","gasLimit":"0x2fefd8","gasUsed":"0xa860","timestamp":"0x1235","extraData":"0x8b726574682f76302e312e30","baseFeePerGas":"0x342770c0","blockHash":"0x5655011482546f16b2312ef18e9fad03d6a52b1be95401aea884b222477f9e64","transactions":["0xf865808506fc23ac00830124f8940000000000000000000000000000000000000316018032a044b25a8b9b247d01586b3d59c71728ff49c9b84928d9e7fa3377ead3b5570b5da03ceac696601ff7ee6f5fe8864e2998db9babdf5eeba1a0cd5b4d44b3fcbd181b"]}"#; let payload: ExecutionPayloadV1 = serde_json::from_str(s).unwrap(); assert_eq!(serde_json::to_string(&payload).unwrap(), s); + + let any_payload: ExecutionPayload = serde_json::from_str(s).unwrap(); + assert_eq!(any_payload, payload.into()); } #[test] @@ -723,6 +682,9 @@ mod tests { let s = r#"{"parentHash":"0x67ead97eb79b47a1638659942384143f36ed44275d4182799875ab5a87324055","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x4e3c608a9f2e129fccb91a1dae7472e78013b8e654bccc8d224ce3d63ae17006","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0x44bb4b98c59dbb726f96ffceb5ee028dcbe35b9bba4f9ffd56aeebf8d1e4db62","blockNumber":"0x1","gasLimit":"0x2fefd8","gasUsed":"0xa860","timestamp":"0x1235","extraData":"0x8b726574682f76302e312e30","baseFeePerGas":"0x342770c0","blockHash":"0x5655011482546f16b2312ef18e9fad03d6a52b1be95401aea884b222477f9e64","transactions":["0xf865808506fc23ac00830124f8940000000000000000000000000000000000000316018032a044b25a8b9b247d01586b3d59c71728ff49c9b84928d9e7fa3377ead3b5570b5da03ceac696601ff7ee6f5fe8864e2998db9babdf5eeba1a0cd5b4d44b3fcbd181b"],"withdrawals":[],"blobGasUsed":"0xb10b","excessBlobGas":"0xb10b"}"#; let payload: ExecutionPayloadV3 = serde_json::from_str(s).unwrap(); assert_eq!(serde_json::to_string(&payload).unwrap(), s); + + let any_payload: ExecutionPayload = serde_json::from_str(s).unwrap(); + assert_eq!(any_payload, payload.into()); } #[test] @@ -731,6 +693,9 @@ mod tests { let s = r#"{"parentHash":"0x67ead97eb79b47a1638659942384143f36ed44275d4182799875ab5a87324055","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x76a03cbcb7adce07fd284c61e4fa31e5e786175cefac54a29e46ec8efa28ea41","receiptsRoot":"0x4e3c608a9f2e129fccb91a1dae7472e78013b8e654bccc8d224ce3d63ae17006","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0x028111cb7d25918386a69656b3d17b2febe95fd0f11572c1a55c14f99fdfe3df","blockNumber":"0x1","gasLimit":"0x2fefd8","gasUsed":"0xa860","timestamp":"0x1235","extraData":"0x8b726574682f76302e312e30","baseFeePerGas":"0x342770c0","blockHash":"0xa6f40ed042e61e88e76125dede8fff8026751ea14454b68fb534cea99f2b2a77","transactions":["0xf865808506fc23ac00830124f8940000000000000000000000000000000000000316018032a044b25a8b9b247d01586b3d59c71728ff49c9b84928d9e7fa3377ead3b5570b5da03ceac696601ff7ee6f5fe8864e2998db9babdf5eeba1a0cd5b4d44b3fcbd181b"]}"#; let payload: ExecutionPayloadV1 = serde_json::from_str(s).unwrap(); assert_eq!(serde_json::to_string(&payload).unwrap(), s); + + let any_payload: ExecutionPayload = serde_json::from_str(s).unwrap(); + assert_eq!(any_payload, payload.into()); } #[test] @@ -739,6 +704,9 @@ mod tests { let s = r#"{"parentHash":"0x67ead97eb79b47a1638659942384143f36ed44275d4182799875ab5a87324055","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x76a03cbcb7adce07fd284c61e4fa31e5e786175cefac54a29e46ec8efa28ea41","receiptsRoot":"0x4e3c608a9f2e129fccb91a1dae7472e78013b8e654bccc8d224ce3d63ae17006","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0x028111cb7d25918386a69656b3d17b2febe95fd0f11572c1a55c14f99fdfe3df","blockNumber":"0x1","gasLimit":"0x2fefd8","gasUsed":"0xa860","timestamp":"0x1235","extraData":"0x8b726574682f76302e312e30","baseFeePerGas":"0x342770c0","blockHash":"0xa6f40ed042e61e88e76125dede8fff8026751ea14454b68fb534cea99f2b2a77","transactions":["0xf865808506fc23ac00830124f8940000000000000000000000000000000000000316018032a044b25a8b9b247d01586b3d59c71728ff49c9b84928d9e7fa3377ead3b5570b5da03ceac696601ff7ee6f5fe8864e2998db9babdf5eeba1a0cd5b4d44b3fcbd181b"],"withdrawals":[],"blobGasUsed":"0xb10b","excessBlobGas":"0xb10b"}"#; let payload: ExecutionPayloadV3 = serde_json::from_str(s).unwrap(); assert_eq!(serde_json::to_string(&payload).unwrap(), s); + + let any_payload: ExecutionPayload = serde_json::from_str(s).unwrap(); + assert_eq!(any_payload, payload.into()); } #[test] @@ -927,7 +895,7 @@ mod tests { #[derive(Serialize, Deserialize)] #[serde(transparent)] struct Event { - #[serde(with = "beacon_api_payload_attributes")] + #[serde(with = "crate::beacon::payload::beacon_api_payload_attributes")] payload: PayloadAttributes, } diff --git a/crates/rpc/rpc-types/src/eth/mod.rs b/crates/rpc/rpc-types/src/eth/mod.rs index 20cdf102f..a1a45d7b2 100644 --- a/crates/rpc/rpc-types/src/eth/mod.rs +++ b/crates/rpc/rpc-types/src/eth/mod.rs @@ -14,7 +14,7 @@ pub mod raw_log; pub mod state; mod syncing; pub mod trace; -mod transaction; +pub mod transaction; pub mod txpool; pub mod withdrawal; mod work; diff --git a/crates/rpc/rpc-types/src/eth/transaction/mod.rs b/crates/rpc/rpc-types/src/eth/transaction/mod.rs index b556e48ca..fcaccafad 100644 --- a/crates/rpc/rpc-types/src/eth/transaction/mod.rs +++ b/crates/rpc/rpc-types/src/eth/transaction/mod.rs @@ -1,3 +1,5 @@ +//! RPC types for transactions + pub use access_list::{AccessList, AccessListItem, AccessListWithGasUsed}; use alloy_primitives::{Address, Bytes, B256, U128, U256, U64}; pub use common::TransactionInfo; diff --git a/crates/rpc/rpc-types/src/eth/withdrawal.rs b/crates/rpc/rpc-types/src/eth/withdrawal.rs index 0ea2322b7..286803f45 100644 --- a/crates/rpc/rpc-types/src/eth/withdrawal.rs +++ b/crates/rpc/rpc-types/src/eth/withdrawal.rs @@ -1,12 +1,10 @@ //! Withdrawal type and serde helpers. -use std::mem; - use crate::serde_helpers::u64_hex; use alloy_primitives::{Address, U256}; use alloy_rlp::{RlpDecodable, RlpEncodable}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use serde_with::{serde_as, DeserializeAs, DisplayFromStr, SerializeAs}; +use serde::{Deserialize, Serialize}; +use std::mem; /// Multiplier for converting gwei to wei. pub const GWEI_TO_WEI: u64 = 1_000_000_000; @@ -42,73 +40,6 @@ impl Withdrawal { } } -/// Same as [Withdrawal] but respects the Beacon API format which uses snake-case and quoted -/// decimals. -#[serde_as] -#[derive(Serialize, Deserialize)] -pub(crate) struct BeaconAPIWithdrawal { - #[serde_as(as = "DisplayFromStr")] - index: u64, - #[serde_as(as = "DisplayFromStr")] - validator_index: u64, - address: Address, - #[serde_as(as = "DisplayFromStr")] - amount: u64, -} - -impl SerializeAs for BeaconAPIWithdrawal { - fn serialize_as(source: &Withdrawal, serializer: S) -> Result - where - S: Serializer, - { - beacon_api_withdrawals::serialize(source, serializer) - } -} - -impl<'de> DeserializeAs<'de, Withdrawal> for BeaconAPIWithdrawal { - fn deserialize_as(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - beacon_api_withdrawals::deserialize(deserializer) - } -} - -/// A helper serde module to convert from/to the Beacon API which uses quoted decimals rather than -/// big-endian hex. -pub mod beacon_api_withdrawals { - use super::*; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - - /// Serialize the payload attributes for the beacon API. - pub fn serialize(payload_attributes: &Withdrawal, serializer: S) -> Result - where - S: Serializer, - { - let withdrawal = BeaconAPIWithdrawal { - index: payload_attributes.index, - validator_index: payload_attributes.validator_index, - address: payload_attributes.address, - amount: payload_attributes.amount, - }; - withdrawal.serialize(serializer) - } - - /// Deserialize the payload attributes for the beacon API. - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let withdrawal = BeaconAPIWithdrawal::deserialize(deserializer)?; - Ok(Withdrawal { - index: withdrawal.index, - validator_index: withdrawal.validator_index, - address: withdrawal.address, - amount: withdrawal.amount, - }) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/rpc/rpc-types/src/lib.rs b/crates/rpc/rpc-types/src/lib.rs index d655fbcce..8c9e22485 100644 --- a/crates/rpc/rpc-types/src/lib.rs +++ b/crates/rpc/rpc-types/src/lib.rs @@ -12,11 +12,13 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] mod admin; +pub mod beacon; mod eth; mod mev; mod net; mod otterscan; mod peer; +pub mod relay; mod rpc; mod serde_helpers; diff --git a/crates/rpc/rpc-types/src/relay.rs b/crates/rpc/rpc-types/src/relay.rs new file mode 100644 index 000000000..c00a33db5 --- /dev/null +++ b/crates/rpc/rpc-types/src/relay.rs @@ -0,0 +1,243 @@ +#![allow(missing_docs)] +//! Relay API bindings + +use crate::{ + beacon::{BlsPublicKey, BlsSignature}, + engine::BlobsBundleV1, + ExecutionPayload, +}; +use alloy_primitives::{Address, B256, U256}; +use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, DisplayFromStr}; + +/// Represents an entry of the `/relay/v1/builder/validators` endpoint +#[serde_as] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Validator { + #[serde_as(as = "DisplayFromStr")] + pub slot: u64, + #[serde_as(as = "DisplayFromStr")] + pub validator_index: u64, + pub entry: ValidatorRegistration, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ValidatorRegistration { + pub message: ValidatorRegistrationMessage, + pub signature: BlsSignature, +} + +#[serde_as] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ValidatorRegistrationMessage { + #[serde(rename = "fee_recipient")] + pub fee_recipient: Address, + #[serde_as(as = "DisplayFromStr")] + pub gas_limit: u64, + #[serde_as(as = "DisplayFromStr")] + pub timestamp: u64, + pub pubkey: BlsPublicKey, +} + +/// Represents public information about a block sent by a builder to the relay, or from the relay to +/// the proposer. Depending on the context, value might represent the claimed value by a builder +/// (not necessarily a value confirmed by the relay). +#[serde_as] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BidTrace { + #[serde_as(as = "DisplayFromStr")] + pub slot: u64, + pub parent_hash: B256, + pub block_hash: B256, + #[serde(rename = "builder_pubkey")] + pub builder_public_key: BlsPublicKey, + #[serde(rename = "proposer_pubkey")] + pub proposer_public_key: BlsPublicKey, + pub proposer_fee_recipient: Address, + #[serde_as(as = "DisplayFromStr")] + pub gas_limit: u64, + #[serde_as(as = "DisplayFromStr")] + pub gas_used: u64, + #[serde_as(as = "DisplayFromStr")] + pub value: U256, +} + +/// SignedBidTrace is a BidTrace with a signature +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SignedBidTrace { + pub message: BidTrace, + pub signature: BlsSignature, +} + +/// Submission for the `/relay/v1/builder/blocks` endpoint. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SignedBidSubmission { + pub message: BidTrace, + pub signature: BlsSignature, + #[serde(with = "crate::beacon::payload::beacon_payload")] + pub execution_payload: ExecutionPayload, + /// The Deneb block bundle for this bid. + /// + /// Only valid for [ExecutionPayloadV3](crate::eth::engine::ExecutionPayloadV3). + // TODO should this be combined with `execution_payload`? + #[serde(default, skip_serializing_if = "Option::is_none")] + pub blobs_bundle: Option, +} + +/// Query for the GET `/relay/v1/data/bidtraces/proposer_payload_delivered` +/// +/// Provides [BidTrace]s for payloads that were delivered to proposers. +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ProposerPayloadsDeliveredQuery { + /// A specific slot + #[serde(skip_serializing_if = "Option::is_none")] + pub slot: Option, + /// Maximum number of entries (200 max) + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + /// Search for a specific blockhash + #[serde(skip_serializing_if = "Option::is_none")] + pub block_hash: Option, + /// Search for a specific EL block number + #[serde(skip_serializing_if = "Option::is_none")] + pub block_number: Option, + /// filter results by a proposer public key + #[serde(skip_serializing_if = "Option::is_none")] + pub proposer_key: Option, + /// How to order results + #[serde(skip_serializing_if = "Option::is_none")] + pub order_by: Option, +} + +impl ProposerPayloadsDeliveredQuery { + /// Sets the specific slot + pub fn slot(mut self, slot: u64) -> Self { + self.slot = Some(slot); + self + } + + /// Sets the maximum number of entries (200 max) + pub fn limit(mut self, limit: u64) -> Self { + self.limit = Some(limit); + self + } + + /// Sets the specific blockhash + pub fn block_hash(mut self, block_hash: B256) -> Self { + self.block_hash = Some(block_hash); + self + } + + /// Sets the specific EL block number + pub fn block_number(mut self, block_number: u64) -> Self { + self.block_number = Some(block_number); + self + } + + /// Sets the proposer public key + pub fn proposer_key(mut self, proposer_key: BlsPublicKey) -> Self { + self.proposer_key = Some(proposer_key); + self + } + + /// Configures how to order results + pub fn order_by(mut self, order_by: OrderBy) -> Self { + self.order_by = Some(order_by); + self + } + + /// Order results by descending value (highest value first) + pub fn order_by_desc(self) -> Self { + self.order_by(OrderBy::Desc) + } + + /// Order results by ascending value (lowest value first) + pub fn order_by_asc(self) -> Self { + self.order_by(OrderBy::Asc) + } +} + +/// OrderBy : Sort results in either ascending or descending values. * `-value` - descending value +/// (highest value first) * `value` - ascending value (lowest value first) Sort results in either +/// ascending or descending values. * `-value` - descending value (highest value first) * `value` +/// - ascending value (lowest value first) +#[derive( + Default, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, +)] +pub enum OrderBy { + /// Sort result by descending value (highest value first) + #[default] + #[serde(rename = "-value")] + Desc, + /// Sort result by ascending value (lowest value first) + #[serde(rename = "value")] + Asc, +} + +/// Query for the GET `/relay/v1/data/bidtraces/builder_blocks_received` endpoint. +/// This endpoint provides BidTraces for the builder block submission for a given slot (that were +/// verified successfully). +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BuilderBlocksReceivedQuery { + /// A specific slot + #[serde(skip_serializing_if = "Option::is_none")] + pub slot: Option, + /// Maximum number of entries (200 max) + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + /// Search for a specific blockhash + #[serde(skip_serializing_if = "Option::is_none")] + pub block_hash: Option, + /// Search for a specific EL block number + #[serde(skip_serializing_if = "Option::is_none")] + pub block_number: Option, +} + +impl BuilderBlocksReceivedQuery { + /// Sets the specific slot + pub fn slot(mut self, slot: u64) -> Self { + self.slot = Some(slot); + self + } + + /// Sets the maximum number of entries (200 max) + pub fn limit(mut self, limit: u64) -> Self { + self.limit = Some(limit); + self + } + + /// Sets the specific blockhash + pub fn block_hash(mut self, block_hash: B256) -> Self { + self.block_hash = Some(block_hash); + self + } + + /// Sets the specific EL block number + pub fn block_number(mut self, block_number: u64) -> Self { + self.block_number = Some(block_number); + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serde_validator() { + let s = r#"[{"slot":"7689441","validator_index":"748813","entry":{"message":{"fee_recipient":"0xbe87be8ac54fb2a4ecb8d7935d0fc80f72c28f9f","gas_limit":"30000000","timestamp":"1688333351","pubkey":"0xb56ff6826cfa6b82fc6c2974988b1576fe5c34bd6c672f911e1d3eec1134822581d6d68f68992ad1f945b0c80468d941"},"signature":"0x8b42028d248f5a2fd41ab425408470ffde1d941ee83db3d9bde583feb22413608673dc27930383893410ef05e52ed8cf0e0291d8ed111189a065f9598176d1c51cabeaba8f628b2f92626bb58d2068292eb7682673a31473d0cdbe278e67c723"}},{"slot":"7689443","validator_index":"503252","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1680328764","pubkey":"0xa8ac80cd889110f407fd6e42d08c71faf158aa917c4c5f5d65bfcea7c4ae5231df9e14721c1980e18233fb1e79316cf6"},"signature":"0xa3e0e3190acc0be2c3a5f9c75cea5d79bebbb955f3f477794e8d7c0cbf73cd61fa0b0c3bfeb5cd9ba53a60c7bf9958640a63ecbd355a8ddb717f2ac8f413dbe4865cbae46291cb590410fa51471df9eaccb3602718df4c17f8eee750df8b5491"}},{"slot":"7689445","validator_index":"792322","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1691185175","pubkey":"0xa25455216e254c96d7ddfc029da033f6de715e91ab09b2fb6a97613b13a8e9189c43f0eaabd671b3c9aab051f6ff0630"},"signature":"0xb63ea72aac017cdfaa7b0fb9cbaffbe3c72b8ce2dd986b6e21834fc2f0accf9184e301de6c5f066bb7075d3f5d020a9e169dee80998685e20553c197ab27ef4d7b0a19f062796a228308ef33a01d0a08dbe35b02e7dca762e247c84e5ea9d170"}},{"slot":"7689446","validator_index":"888141","entry":{"message":{"fee_recipient":"0x73b9067aeeeab2e6263951ad9637971145566ba6","gas_limit":"30000000","timestamp":"1692606575","pubkey":"0x921b0309fffd798599f9564103f9ab34ebe2a3ea17ab39439e5e033ec5affb925a5ad65c0863a1d0f84365e4c1eec952"},"signature":"0x8a88068c7926d81348b773977050d093450b673af1762c0f0b416e4fcc76f277f2f117138780e62e49f5ac02d13ba5ed0e2d76df363003c4ff7ad40713ed3ef6aa3eb57580c8a3e1e6fe7245db700e541d1f2a7e88ec426c3fba82fa91b647a4"}},{"slot":"7689451","validator_index":"336979","entry":{"message":{"fee_recipient":"0xebec795c9c8bbd61ffc14a6662944748f299cacf","gas_limit":"30000000","timestamp":"1680621366","pubkey":"0x8b46eb0f36a51fcfab66910380be4d68c6323291eada9f68ad628a798a9c21ed858d62f1b15c08484b13c9cdcd0fc657"},"signature":"0x910afc415aed14a0c49cc1c2d29743018a69e517de84cee9e4ff2ea21a0d3b95c25b0cd59b8a2539fbe8e73d5e53963a0afbcf9f86db4de4ba03aa1f80d9dc1ecca18e597829e5e9269ce08b99ff347eba43c0d9c87c174f3a30422d4de800c8"}},{"slot":"7689452","validator_index":"390650","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1678460723","pubkey":"0x854464f0b798d1510a0f76a77190f15e9e67d5ac348647f5fe906539cf4ff7101fb1463f4c408b72e6fae9cfbd21ffd3"},"signature":"0xb5c3aa515cdf723f03fafd092150d6fc5453f6bcec873194927f64f65aa96241f4c5ed417e0676163c5a07af0d63f83811268096e520af3b6a5d5031e619609a0999efc03cc94a30a1175e5e5a52c66d868ebb527669be27a7b81920e44c511a"}},{"slot":"7689453","validator_index":"316626","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1680791443","pubkey":"0xa7a4ecdc53283698af917eaf0ba68e350f5cd136c2b12fa10e59f1e1fd15130da1889a28975777887d84638c85e9036c"},"signature":"0xa3f7604dafd63d9a22d66c228d7d90e83a69d9fa4b920dceeb2e7d43ef49307645725f7c4890fefce18ba41d36b4d23c09cef5109827c7fb3bc78f8526ba0bfeceb950a6f0b5d5d8ad1c8dc740267f053b4f9113141c27e1528c8557b9175e3f"}},{"slot":"7689455","validator_index":"733684","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1688895576","pubkey":"0xb2731b8efe43d723c4b621bd23e48dd00e1fe7816414e708f3607da6f17f5ba5c172ceac005591cb3e28320ae6aa81cf"},"signature":"0x85c8fd5201705413e004b0b4ef773c28d95864deab2e7698da6ea8b27c46867d03e50dae4ad523cebc1ea6205b7925810347e14f8db5396d11200c0cd099faefe254bc2844b29bf15d4d62e15f876f08ee53e2cd33ceee698d69f4f70e8eac82"}},{"slot":"7689457","validator_index":"950865","entry":{"message":{"fee_recipient":"0x8b733fe59d1190b3efa5be3f21a574ef2ab0c62b","gas_limit":"30000000","timestamp":"1696575191","pubkey":"0xa7c8fbb503de34fb92d43490533c35f0418a52ff5834462213950217b592f975caa3ac1daa3f1cdd89362d6f48ef46c1"},"signature":"0x9671727de015aa343db8a8068e27b555b59b6dcfc9b7f8b47bce820fe60cd1179dfdfc91270918edf40a918b513e5a16069e23aaf1cdd37fce0d63e3ade7ca9270ed4a64f70eb64915791e47074bf76aa3225ebd727336444b44826f2cf2a002"}},{"slot":"7689459","validator_index":"322181","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1677755315","pubkey":"0x9381507f58bd51e0ce662e6bb27796e416988bd1f64f219f8cffd2137e933a5fb4c8196ca80c653fd5f69ef378f038aa"},"signature":"0xaeb27e1b729dded42d28efcfadfc6691851749ccb19779f488a37a31e12a56cfd856e81d251fe0e7868aa539f66116b11216a9599bd51a06f386d2255381fcd9d7c698df980964a7e54428cee458e28e3ca5078db92e6837cba72276e7af3334"}},{"slot":"7689461","validator_index":"482285","entry":{"message":{"fee_recipient":"0x6d2e03b7effeae98bd302a9f836d0d6ab0002766","gas_limit":"30000000","timestamp":"1680010474","pubkey":"0x940c6e411db07f151c7c647cb09842f5979db8d89c11c3c1a08894588f1d561e59bc15bd085dc9a025aac52b1cf83d73"},"signature":"0xb4b9fab65a2c01208fd17117b83d59b1e0bb92ede9f7ac3f48f55957551b36add2c3d880e76118fecf1e01496bc3b065194ae6bcb317f1c583f70e2a67cf2a28f4f68d80fc3697941d3700468ac29aafd0118778112d253eb3c31d6bcbdc0c13"}},{"slot":"7689466","validator_index":"98883","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1680479471","pubkey":"0x8d50dad3c465c2c5cd327fd725e239915c0ba43adfdc106909665222c43b2705e9db77f8102308445ae5301131d2a843"},"signature":"0x8a597b9c3160f12bed715a0311134ce04875d05050eb6a349fcc470a610f039ce5a07eebf5332db4f2126b77ebdd1beb0df83784e01061489333aba009ecdb9767e61933f78d2fd96520769afffa3be4455d8dfc6de3fb1b2cee2133f8dd15cf"}},{"slot":"7689470","validator_index":"533204","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1695122498","pubkey":"0x88b31798f15c2857200c60ac923c5a310c204edbce0d49c755f3f0c65e688ab065870385a5b2b18972917c566ecc02a4"},"signature":"0x96033c3ac9d7083d889a723ecd99c79cb2ab3caebeac5435d2319fd300b796ca4f6261eca211b0dbb6df98ce23eba75b04203e026c5aee373d2ba579904ea06284ff58df7bd45ea9a4d9cc9b24ef15ee57e8894193d1c6c8883dace63feb77b7"}},{"slot":"7689471","validator_index":"129205","entry":{"message":{"fee_recipient":"0xed33259a056f4fb449ffb7b7e2ecb43a9b5685bf","gas_limit":"30000000","timestamp":"1695122497","pubkey":"0xb83dc440882e55185ef74631329f954e0f2d547e4f8882bd4470698d1883754eb9b5ee17a091de9730c80d5d1982f6e7"},"signature":"0xabc4d7cc48c2b4608ba49275620837a543e8a6a79d65395c9fca9794442acacf6df2fb1aca71f85472c521c4cf5797f702bd8adef32d7cd38d98334a0a11f8d197a702fa70f48d8512676e64e55a98914a0acc89b60a37046efb646f684a3917"}},{"slot":"7689472","validator_index":"225708","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1680528109","pubkey":"0xa35ae9c944764f4abb247471ad4744b5acec3156a5ec0e72f67a4417268c7c0a8c2775a9639e70ef345176c41b3cd1ba"},"signature":"0x96dd275d5eadd7648e0b8ef44070da65bd2e8d597b51e292d1f775af36595300dcd69e466b51e422f938e695c9cbacd71249d7dfadc9fdf358170082645f63a2ddc6fd82e6a68100460b6beac7603af09012ef06705900410204e8adb6c08d21"}},{"slot":"7689473","validator_index":"959108","entry":{"message":{"fee_recipient":"0xf197c6f2ac14d25ee2789a73e4847732c7f16bc9","gas_limit":"30000000","timestamp":"1696666607","pubkey":"0xaa4c7bc4848f7ea9939112c726e710db5603bc562ef006b6bf5f4644f31b9ab4daf8e3ff72f0140c077f756073dbe3bd"},"signature":"0x8de420ab9db85a658c2ba5feb59020d1e5c0c5e385f55cb877304a348609ad64ec3f3d7be1c6e847df8687bf9c045f2c06e56b4302a04df07801fbcccaf32dbeaeb854680536e13c7c2dc9372272fbf65651298bfb12bdedb58ddda3de5b26c2"}},{"slot":"7689477","validator_index":"258637","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1693935108","pubkey":"0x944bad9bc9ad0fa7b287b1e85e3bf0a5e246c871f2ce62c974d46b2968f853bdc06c22933e2be0549967343d4b01310b"},"signature":"0x906badf5ea9b63a210e7e5baa2f9b4871c9176a26b55282b148fb6eb3f433a41cabe61be612b02c1d6c071f13a374ee118b1fe71d0461c12a9595e0ed458123b0a1bbfef389aec8954803af60eca8ae982f767aa2f7f4c051f38ef630eaef8bf"}},{"slot":"7689479","validator_index":"641748","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1685364312","pubkey":"0xaf548fad3364238b9a909dc56735249b91e0fd5e330f65aa9072fe7f73024b4d8febc7cc2bd401ad8ace9a1043440b22"},"signature":"0xb36cb46aeb188463e9fec2f89b6dcb2e52489af7c852915011ff625fb24d82ded781ae864ccbd354cbbed1f02037a67a152fecc2735b598ab31c4620e7151dd20eb761c620c49dcb31704f43b631979fc545be4292bc0f193a1919255db7a5b8"}},{"slot":"7689481","validator_index":"779837","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1687790504","pubkey":"0xa39f287b3195ecaeb1c18c4f957d881a86a31f809078fe1d7525acfa751b7b0c43f369813d63d72fdd35d0e58f73bea9"},"signature":"0xb0bd69f16df95d09f02768c0625eb1d99dd6385a7db8aa72606a4b0b05a36add5671b02c554914e087f302bf9e17698f0b551a5b517ebdad68118f672bf80ea8de118d9e06808a39cf107bbc13a0cdfbfd0d5e1daf39ad4d66364a0047609dea"}},{"slot":"7689484","validator_index":"903816","entry":{"message":{"fee_recipient":"0xebec795c9c8bbd61ffc14a6662944748f299cacf","gas_limit":"30000000","timestamp":"1695066839","pubkey":"0xa1401efd4b681f3123da1c5de0f786e8c7879ceebc399227db994debf416e198ec25afecd1ee443808affd93143d183e"},"signature":"0x90234ccb98ca78ba35ae5925af7eb985c3cf6fd5f94291f881f315cf1072ab45a7dd812af52d8aede2f08d8179a5a7eb02b2b01fc8a2a792ef7077010df9f444866a03b8ec4798834dc9af8ff55fcd52f399b41d9dd9b0959d456f24aa38ac3c"}},{"slot":"7689485","validator_index":"364451","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1678669663","pubkey":"0x879658976e272aafab49be7105b27f9dea07e79f386dc4a165e17b082059c4b5628d8677203cd809a9332580d9cc28fe"},"signature":"0x8a3013425fd933630521a88a96dcc51e82f8f23cc5c243102415f7782799d39a3450bc5dc6b8a59331f78199e7729896186e700b166841195057ed6efbbd36a5a352cbe6a69ecbec27d74f9b2336f290e19940623a9712b70b59724f879f4e77"}},{"slot":"7689487","validator_index":"641598","entry":{"message":{"fee_recipient":"0x2370d6d6a4e6de417393d54abb144e740f662e01","gas_limit":"30000000","timestamp":"1683216978","pubkey":"0xa226a5c9121aee6a0e20759d877386bb50cb07de978eb89cb0a09dd9d7159117e4d610b3b80233c48bd84a1b9df5f1b2"},"signature":"0x983a0e5721dc39ae3bc35a83999f964ff7840e25e1033f033d51f40408acd07b4f8bda2bbd27f9fe793dd26e2dfe150c03577d0e2ff16d88cef0fb3bb849602d7287aac89199a4b39b1903a8dd9cd9e206ff68c391732fc6e6ef5ff2c89cb439"}},{"slot":"7689490","validator_index":"954682","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1696695974","pubkey":"0xadbe2aeecfc01016938dc0a90986d36975acdd1e3cbb3461bb917a7eaf260c1a387867e47f3d9a1dd56778f552a0ed6a"},"signature":"0x85dea1b1b01ecaf3240571ecddcfc4eaa06b4e23b1c2cc6db646164716f96b8ad46bf0687f5bb840a7468514ac18439205bfb16941cdafc6853c4c65271cd113be72f9428469d0815a7169c70ae37709a19ad669e709d6a9cfd90bc471309bc6"}},{"slot":"7689496","validator_index":"833362","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1690132607","pubkey":"0xa4e9ee836facfaf67dab10c59c10ed6d3a0f007799129f13b009be56ed87ad6b08b30b315f38b6cc38f2fdb747dac587"},"signature":"0xb73b40c9766004d6471b3355fc6ffa765a442c0687852687ed89120cdcebf03c37ed2c087fd254492b2b7f11c2446ec5116e40f442367196af967e6905ca5fb333a2b3a9705c0d302817038199b43c1dd36124fe6085d610c491176d1d5d0cff"}},{"slot":"7689498","validator_index":"202233","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1678891455","pubkey":"0x8cb0401c024bb74a481b9253ce61af57ad263e7ab542166922c13e46d76c2593b4735a85e5fbaba9d1cd63d8996981e1"},"signature":"0xb9364c714be2c11651b8c6854b0fc5872d8b109fa1c8b67e4e1bf71555a364e3003a88d98292a09487d20b3e89a0716c1030a559ce70aeef582fab3d6820fde678249d3952c809c53e56940cc74ba6fcc112bb94adf72d41451e5e69788f98da"}},{"slot":"7689500","validator_index":"380674","entry":{"message":{"fee_recipient":"0x388c818ca8b9251b393131c08a736a67ccb19297","gas_limit":"30000000","timestamp":"1693299181","pubkey":"0x9801236e1e78426d7b4be81f828419bd1aac3b2041ebed2af9683eca259e9915c6f5d45d9e3021a8218f8b6a73550ee4"},"signature":"0x98961cb2f920898e656ddaf66d11bcfd808179cf8f313687260eb760bd065f1f5ae79a37587575a1c6c46671845c63e409cc01bca97823adc0e6dbbc280327df886df4fb8aa7e1d310311bc80e29fed90a6ae3346017d1b5d20b32beed8fd477"}},{"slot":"7689502","validator_index":"486859","entry":{"message":{"fee_recipient":"0x0b91a6bafdae6ae32d864ed9a0e883a5ca9a02dd","gas_limit":"30000000","timestamp":"1680003847","pubkey":"0x99225f70bb2c9310835a367e95b34c0d6021b5ec9bf350f4d8f0fc4dce34c9c16f4299b788ad0d27001b0efd2712d494"},"signature":"0x86ab16d4b4e686b20d1bb9d532975961acd797e02d9c9e48d13805ec2ba71df9e69a63c3b254b8e640fcc26f651ad243155430095caa6c5770b52039f1d6a9312e0d8f9dd2fb4fe2d35d372075a93b14e745be91e7eb1f28f0f5bf2c62f7584e"}},{"slot":"7689503","validator_index":"70348","entry":{"message":{"fee_recipient":"0x6d2e03b7effeae98bd302a9f836d0d6ab0002766","gas_limit":"30000000","timestamp":"1680011532","pubkey":"0x820dd8b5396377da3f2d4972d4c94fbad401bbf4b3a56e570a532f77c1802f2cc310bf969bb6aa96d90ea2708c562ed6"},"signature":"0xb042608d02c4ca053c6b9e22804af99421c47eda96ce585d1df6f37cbf59cfd6830a3592f6de543232135c42bb3da9cd13ecf5aea2e3b802114dc08877e9023a7cf6d75e28ca30d1df3f3c98bddd36b4f521e63895179a7e8c3752f5cbc681ea"}}]"#; + + let validators: Vec = serde_json::from_str(s).unwrap(); + let json: serde_json::Value = serde_json::from_str(s).unwrap(); + assert_eq!(json, serde_json::to_value(validators).unwrap()); + } + + #[test] + fn deneb_bid_submission() { + let s = r#"{"message":{"slot":"1","parent_hash":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","block_hash":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2","builder_pubkey":"0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a", "proposer_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a","proposer_fee_recipient":"0xabcf8e0d4e9587369b2301d0790347320302cc09","gas_limit":"1","gas_used":"1","value":"1"},"execution_payload":{"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":["0x02f878831469668303f51d843b9ac9f9843b9aca0082520894c93269b73096998db66be0441e836d873535cb9c8894a19041886f000080c001a031cc29234036afbf9a1fb9476b463367cb1f957ac0b919b69bbc798436e604aaa018c4e9c3914eb27aadd0b91e10b18655739fcf8c1fc398763a9f1beecb8ddc86"],"withdrawals":[{"index":"1","validator_index":"1","address":"0xabcf8e0d4e9587369b2301d0790347320302cc09","amount":"32000000000"}]},"blobs_bundle":{"commitments":[],"proofs":[],"blobs":[]},"signature":"0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"}"#; + + let bid = serde_json::from_str::(s).unwrap(); + let json: serde_json::Value = serde_json::from_str(s).unwrap(); + assert_eq!(json, serde_json::to_value(bid).unwrap()); + } +} diff --git a/examples/beacon-api-sse/src/main.rs b/examples/beacon-api-sse/src/main.rs index 04f0be111..ccf3d1e5c 100644 --- a/examples/beacon-api-sse/src/main.rs +++ b/examples/beacon-api-sse/src/main.rs @@ -23,7 +23,7 @@ use reth::{ ext::{RethCliExt, RethNodeCommandConfig}, Cli, }, - rpc::types::engine::beacon_api::events::PayloadAttributesEvent, + rpc::types::beacon::events::PayloadAttributesEvent, tasks::TaskSpawner, }; use std::net::{IpAddr, Ipv4Addr};