resolve merge conflict and migrate il service to new pardigmn

This commit is contained in:
Eitan Seri-Levi
2025-05-21 12:43:43 -07:00
358 changed files with 11541 additions and 6759 deletions

View File

@@ -19,6 +19,7 @@ mediatype = "0.19.13"
multiaddr = "0.18.2"
pretty_reqwest_error = { workspace = true }
proto_array = { workspace = true }
rand = { workspace = true }
reqwest = { workspace = true }
reqwest-eventsource = "0.5.0"
sensitive_url = { workspace = true }
@@ -26,6 +27,7 @@ serde = { workspace = true }
serde_json = { workspace = true }
slashing_protection = { workspace = true }
ssz_types = { workspace = true }
test_random_derive = { path = "../../common/test_random_derive" }
types = { workspace = true }
zeroize = { workspace = true }

View File

@@ -16,7 +16,7 @@ pub mod types;
use self::mixin::{RequestAccept, ResponseOptional};
use self::types::{Error as ResponseError, *};
use ::types::fork_versioned_response::ExecutionOptimisticFinalizedForkVersionedResponse;
use ::types::beacon_response::ExecutionOptimisticFinalizedBeaconResponse;
use derivative::Derivative;
use either::Either;
use futures::Stream;
@@ -56,7 +56,7 @@ pub enum Error {
/// The `reqwest` client raised an error.
HttpClient(PrettyReqwestError),
/// The `reqwest_eventsource` client raised an error.
SseClient(reqwest_eventsource::Error),
SseClient(Box<reqwest_eventsource::Error>),
/// The server returned an error message where the body was able to be parsed.
ServerMessage(ErrorMessage),
/// The server returned an error message with an array of errors.
@@ -99,7 +99,7 @@ impl Error {
match self {
Error::HttpClient(error) => error.inner().status(),
Error::SseClient(error) => {
if let reqwest_eventsource::Error::InvalidStatusCode(status, _) = error {
if let reqwest_eventsource::Error::InvalidStatusCode(status, _) = error.as_ref() {
Some(*status)
} else {
None
@@ -146,6 +146,7 @@ pub struct Timeouts {
pub get_debug_beacon_states: Duration,
pub get_deposit_snapshot: Duration,
pub get_validator_block: Duration,
pub default: Duration,
}
impl Timeouts {
@@ -165,6 +166,7 @@ impl Timeouts {
get_debug_beacon_states: timeout,
get_deposit_snapshot: timeout,
get_validator_block: timeout,
default: timeout,
}
}
}
@@ -239,7 +241,9 @@ impl BeaconNodeHttpClient {
url: U,
builder: impl FnOnce(RequestBuilder) -> RequestBuilder,
) -> Result<Response, Error> {
let response = builder(self.client.get(url)).send().await?;
let response = builder(self.client.get(url).timeout(self.timeouts.default))
.send()
.await?;
ok_or_error(response).await
}
@@ -283,6 +287,54 @@ impl BeaconNodeHttpClient {
}
}
pub async fn get_fork_contextual<T, U, Ctx, Meta>(
&self,
url: U,
ctx_constructor: impl Fn(ForkName) -> Ctx,
) -> Result<Option<ForkVersionedResponse<T, Meta>>, Error>
where
U: IntoUrl,
T: ContextDeserialize<'static, Ctx>,
Meta: DeserializeOwned,
Ctx: Clone,
{
let response = self
.get_response(url, |b| b.accept(Accept::Json))
.await
.optional()?;
let Some(resp) = response else {
return Ok(None);
};
let bytes = resp.bytes().await?;
#[derive(serde::Deserialize)]
struct Helper {
// TODO: remove this default once checkpointz follows the spec
#[serde(default = "ForkName::latest_stable")]
version: ForkName,
#[serde(flatten)]
metadata: serde_json::Value,
data: serde_json::Value,
}
let helper: Helper = serde_json::from_slice(&bytes).map_err(Error::InvalidJson)?;
let metadata: Meta = serde_json::from_value(helper.metadata).map_err(Error::InvalidJson)?;
let ctx = ctx_constructor(helper.version);
let data: T = ContextDeserialize::context_deserialize(helper.data, ctx)
.map_err(Error::InvalidJson)?;
Ok(Some(ForkVersionedResponse {
version: helper.version,
metadata,
data,
}))
}
/// Perform a HTTP GET request using an 'accept' header, returning `None` on a 404 error.
pub async fn get_bytes_opt_accept_header<U: IntoUrl>(
&self,
@@ -402,11 +454,10 @@ impl BeaconNodeHttpClient {
body: &T,
timeout: Option<Duration>,
) -> Result<Response, Error> {
let mut builder = self.client.post(url);
if let Some(timeout) = timeout {
builder = builder.timeout(timeout);
}
let builder = self
.client
.post(url)
.timeout(timeout.unwrap_or(self.timeouts.default));
let response = builder.json(body).send().await?;
ok_or_error(response).await
}
@@ -419,10 +470,10 @@ impl BeaconNodeHttpClient {
timeout: Option<Duration>,
fork: ForkName,
) -> Result<Response, Error> {
let mut builder = self.client.post(url);
if let Some(timeout) = timeout {
builder = builder.timeout(timeout);
}
let builder = self
.client
.post(url)
.timeout(timeout.unwrap_or(self.timeouts.default));
let response = builder
.header(CONSENSUS_VERSION_HEADER, fork.to_string())
.json(body)
@@ -437,7 +488,7 @@ impl BeaconNodeHttpClient {
url: U,
body: &T,
) -> Result<Response, Error> {
let builder = self.client.post(url);
let builder = self.client.post(url).timeout(self.timeouts.default);
let mut headers = HeaderMap::new();
headers.insert(
@@ -456,10 +507,10 @@ impl BeaconNodeHttpClient {
timeout: Option<Duration>,
fork: ForkName,
) -> Result<Response, Error> {
let mut builder = self.client.post(url);
if let Some(timeout) = timeout {
builder = builder.timeout(timeout);
}
let builder = self
.client
.post(url)
.timeout(timeout.unwrap_or(self.timeouts.default));
let mut headers = HeaderMap::new();
headers.insert(
CONSENSUS_VERSION_HEADER,
@@ -824,6 +875,26 @@ impl BeaconNodeHttpClient {
self.get_opt(path).await
}
/// `GET beacon/states/{state_id}/pending_consolidations`
///
/// Returns `Ok(None)` on a 404 error.
pub async fn get_beacon_states_pending_consolidations(
&self,
state_id: StateId,
) -> Result<Option<ExecutionOptimisticFinalizedResponse<Vec<PendingConsolidation>>>, Error>
{
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("beacon")
.push("states")
.push(&state_id.to_string())
.push("pending_consolidations");
self.get_opt(path).await
}
/// `GET beacon/light_client/updates`
///
/// Returns `Ok(None)` on a 404 error.
@@ -831,7 +902,7 @@ impl BeaconNodeHttpClient {
&self,
start_period: u64,
count: u64,
) -> Result<Option<Vec<ForkVersionedResponse<LightClientUpdate<E>>>>, Error> {
) -> Result<Option<Vec<BeaconResponse<LightClientUpdate<E>>>>, Error> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
@@ -846,7 +917,14 @@ impl BeaconNodeHttpClient {
path.query_pairs_mut()
.append_pair("count", &count.to_string());
self.get_opt(path).await
self.get_opt(path).await.map(|opt| {
opt.map(|updates: Vec<_>| {
updates
.into_iter()
.map(BeaconResponse::ForkVersioned)
.collect()
})
})
}
/// `GET beacon/light_client/bootstrap`
@@ -855,7 +933,7 @@ impl BeaconNodeHttpClient {
pub async fn get_light_client_bootstrap<E: EthSpec>(
&self,
block_root: Hash256,
) -> Result<Option<ForkVersionedResponse<LightClientBootstrap<E>>>, Error> {
) -> Result<Option<BeaconResponse<LightClientBootstrap<E>>>, Error> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
@@ -865,7 +943,9 @@ impl BeaconNodeHttpClient {
.push("bootstrap")
.push(&format!("{:?}", block_root));
self.get_opt(path).await
self.get_opt(path)
.await
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
}
/// `GET beacon/light_client/optimistic_update`
@@ -873,7 +953,7 @@ impl BeaconNodeHttpClient {
/// Returns `Ok(None)` on a 404 error.
pub async fn get_beacon_light_client_optimistic_update<E: EthSpec>(
&self,
) -> Result<Option<ForkVersionedResponse<LightClientOptimisticUpdate<E>>>, Error> {
) -> Result<Option<BeaconResponse<LightClientOptimisticUpdate<E>>>, Error> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
@@ -882,7 +962,9 @@ impl BeaconNodeHttpClient {
.push("light_client")
.push("optimistic_update");
self.get_opt(path).await
self.get_opt(path)
.await
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
}
/// `GET beacon/light_client/finality_update`
@@ -890,7 +972,7 @@ impl BeaconNodeHttpClient {
/// Returns `Ok(None)` on a 404 error.
pub async fn get_beacon_light_client_finality_update<E: EthSpec>(
&self,
) -> Result<Option<ForkVersionedResponse<LightClientFinalityUpdate<E>>>, Error> {
) -> Result<Option<BeaconResponse<LightClientFinalityUpdate<E>>>, Error> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
@@ -899,7 +981,9 @@ impl BeaconNodeHttpClient {
.push("light_client")
.push("finality_update");
self.get_opt(path).await
self.get_opt(path)
.await
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
}
/// `GET beacon/headers?slot,parent_root`
@@ -962,8 +1046,14 @@ impl BeaconNodeHttpClient {
.push("beacon")
.push("blocks");
self.post_with_timeout(path, block_contents, self.timeouts.proposal)
.await?;
let fork_name = block_contents.signed_block().fork_name_unchecked();
self.post_generic_with_consensus_version(
path,
block_contents,
Some(self.timeouts.proposal),
fork_name,
)
.await?;
Ok(())
}
@@ -1181,16 +1271,12 @@ impl BeaconNodeHttpClient {
pub async fn get_beacon_blocks<E: EthSpec>(
&self,
block_id: BlockId,
) -> Result<
Option<ExecutionOptimisticFinalizedForkVersionedResponse<SignedBeaconBlock<E>>>,
Error,
> {
) -> Result<Option<ExecutionOptimisticFinalizedBeaconResponse<SignedBeaconBlock<E>>>, Error>
{
let path = self.get_beacon_blocks_path(block_id)?;
let Some(response) = self.get_response(path, |b| b).await.optional()? else {
return Ok(None);
};
Ok(Some(response.json().await?))
self.get_opt(path)
.await
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
}
/// `GET v1/beacon/blob_sidecars/{block_id}`
@@ -1200,8 +1286,8 @@ impl BeaconNodeHttpClient {
&self,
block_id: BlockId,
indices: Option<&[u64]>,
) -> Result<Option<ExecutionOptimisticFinalizedForkVersionedResponse<BlobSidecarList<E>>>, Error>
{
spec: &ChainSpec,
) -> Result<Option<ExecutionOptimisticFinalizedBeaconResponse<BlobSidecarList<E>>>, Error> {
let mut path = self.get_blobs_path(block_id)?;
if let Some(indices) = indices {
let indices_string = indices
@@ -1213,11 +1299,11 @@ impl BeaconNodeHttpClient {
.append_pair("indices", &indices_string);
}
let Some(response) = self.get_response(path, |b| b).await.optional()? else {
return Ok(None);
};
Ok(Some(response.json().await?))
self.get_fork_contextual(path, |fork| {
(fork, spec.max_blobs_per_block_by_fork(fork) as usize)
})
.await
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
}
/// `GET v1/beacon/blinded_blocks/{block_id}`
@@ -1227,15 +1313,13 @@ impl BeaconNodeHttpClient {
&self,
block_id: BlockId,
) -> Result<
Option<ExecutionOptimisticFinalizedForkVersionedResponse<SignedBlindedBeaconBlock<E>>>,
Option<ExecutionOptimisticFinalizedBeaconResponse<SignedBlindedBeaconBlock<E>>>,
Error,
> {
let path = self.get_beacon_blinded_blocks_path(block_id)?;
let Some(response) = self.get_response(path, |b| b).await.optional()? else {
return Ok(None);
};
Ok(Some(response.json().await?))
self.get_opt(path)
.await
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
}
/// `GET v1/beacon/blocks` (LEGACY)
@@ -1244,7 +1328,7 @@ impl BeaconNodeHttpClient {
pub async fn get_beacon_blocks_v1<E: EthSpec>(
&self,
block_id: BlockId,
) -> Result<Option<ForkVersionedResponse<SignedBeaconBlock<E>>>, Error> {
) -> Result<Option<BeaconResponse<SignedBeaconBlock<E>>>, Error> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
@@ -1253,7 +1337,9 @@ impl BeaconNodeHttpClient {
.push("blocks")
.push(&block_id.to_string());
self.get_opt(path).await
self.get_opt(path)
.await
.map(|opt| opt.map(BeaconResponse::Unversioned))
}
/// `GET beacon/blocks` as SSZ
@@ -1334,7 +1420,7 @@ impl BeaconNodeHttpClient {
pub async fn get_beacon_blocks_attestations_v2<E: EthSpec>(
&self,
block_id: BlockId,
) -> Result<Option<ExecutionOptimisticFinalizedForkVersionedResponse<Vec<Attestation<E>>>>, Error>
) -> Result<Option<ExecutionOptimisticFinalizedBeaconResponse<Vec<Attestation<E>>>>, Error>
{
let mut path = self.eth_path(V2)?;
@@ -1345,7 +1431,9 @@ impl BeaconNodeHttpClient {
.push(&block_id.to_string())
.push("attestations");
self.get_opt(path).await
self.get_opt(path)
.await
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
}
/// `POST v1/beacon/pool/attestations`
@@ -1437,7 +1525,7 @@ impl BeaconNodeHttpClient {
&self,
slot: Option<Slot>,
committee_index: Option<u64>,
) -> Result<ForkVersionedResponse<Vec<Attestation<E>>>, Error> {
) -> Result<BeaconResponse<Vec<Attestation<E>>>, Error> {
let mut path = self.eth_path(V2)?;
path.path_segments_mut()
@@ -1456,7 +1544,7 @@ impl BeaconNodeHttpClient {
.append_pair("committee_index", &index.to_string());
}
self.get(path).await
self.get(path).await.map(BeaconResponse::ForkVersioned)
}
/// `POST v1/beacon/pool/attester_slashings`
@@ -1515,7 +1603,7 @@ impl BeaconNodeHttpClient {
/// `GET v2/beacon/pool/attester_slashings`
pub async fn get_beacon_pool_attester_slashings_v2<E: EthSpec>(
&self,
) -> Result<ForkVersionedResponse<Vec<AttesterSlashing<E>>>, Error> {
) -> Result<BeaconResponse<Vec<AttesterSlashing<E>>>, Error> {
let mut path = self.eth_path(V2)?;
path.path_segments_mut()
@@ -1524,7 +1612,7 @@ impl BeaconNodeHttpClient {
.push("pool")
.push("attester_slashings");
self.get(path).await
self.get(path).await.map(BeaconResponse::ForkVersioned)
}
/// `POST beacon/pool/proposer_slashings`
@@ -1871,7 +1959,13 @@ impl BeaconNodeHttpClient {
.push("node")
.push("health");
let status = self.client.get(path).send().await?.status();
let status = self
.client
.get(path)
.timeout(self.timeouts.default)
.send()
.await?
.status();
if status == StatusCode::OK || status == StatusCode::PARTIAL_CONTENT {
Ok(status)
} else {
@@ -1958,10 +2052,11 @@ impl BeaconNodeHttpClient {
pub async fn get_debug_beacon_states<E: EthSpec>(
&self,
state_id: StateId,
) -> Result<Option<ExecutionOptimisticFinalizedForkVersionedResponse<BeaconState<E>>>, Error>
{
) -> Result<Option<ExecutionOptimisticFinalizedBeaconResponse<BeaconState<E>>>, Error> {
let path = self.get_debug_beacon_states_path(state_id)?;
self.get_opt(path).await
self.get_opt(path)
.await
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
}
/// `GET debug/beacon/states/{state_id}`
@@ -2045,7 +2140,7 @@ impl BeaconNodeHttpClient {
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
) -> Result<ForkVersionedResponse<FullBlockContents<E>>, Error> {
) -> Result<BeaconResponse<FullBlockContents<E>>, Error> {
self.get_validator_blocks_modular(slot, randao_reveal, graffiti, SkipRandaoVerification::No)
.await
}
@@ -2057,12 +2152,12 @@ impl BeaconNodeHttpClient {
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
) -> Result<ForkVersionedResponse<FullBlockContents<E>>, Error> {
) -> Result<BeaconResponse<FullBlockContents<E>>, Error> {
let path = self
.get_validator_blocks_path::<E>(slot, randao_reveal, graffiti, skip_randao_verification)
.await?;
self.get(path).await
self.get(path).await.map(BeaconResponse::ForkVersioned)
}
/// returns `GET v2/validator/blocks/{slot}` URL path
@@ -2318,7 +2413,7 @@ impl BeaconNodeHttpClient {
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
) -> Result<ForkVersionedResponse<BlindedBeaconBlock<E>>, Error> {
) -> Result<BeaconResponse<BlindedBeaconBlock<E>>, Error> {
self.get_validator_blinded_blocks_modular(
slot,
randao_reveal,
@@ -2367,7 +2462,7 @@ impl BeaconNodeHttpClient {
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
) -> Result<ForkVersionedResponse<BlindedBeaconBlock<E>>, Error> {
) -> Result<BeaconResponse<BlindedBeaconBlock<E>>, Error> {
let path = self
.get_validator_blinded_blocks_path::<E>(
slot,
@@ -2377,7 +2472,7 @@ impl BeaconNodeHttpClient {
)
.await?;
self.get(path).await
self.get(path).await.map(BeaconResponse::ForkVersioned)
}
/// `GET v2/validator/blinded_blocks/{slot}` in ssz format
@@ -2466,7 +2561,7 @@ impl BeaconNodeHttpClient {
slot: Slot,
attestation_data_root: Hash256,
committee_index: CommitteeIndex,
) -> Result<Option<ForkVersionedResponse<Attestation<E>>>, Error> {
) -> Result<Option<BeaconResponse<Attestation<E>>>, Error> {
let mut path = self.eth_path(V2)?;
path.path_segments_mut()
@@ -2484,6 +2579,7 @@ impl BeaconNodeHttpClient {
self.get_opt_with_timeout(path, self.timeouts.attestation)
.await
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
}
/// `GET validator/sync_committee_contribution`
@@ -2729,7 +2825,7 @@ impl BeaconNodeHttpClient {
while let Some(event) = es.next().await {
match event {
Ok(Event::Open) => break,
Err(err) => return Err(Error::SseClient(err)),
Err(err) => return Err(Error::SseClient(err.into())),
// This should never happen as we are guaranteed to get the
// Open event before any message starts coming through.
Ok(Event::Message(_)) => continue,
@@ -2741,7 +2837,7 @@ impl BeaconNodeHttpClient {
Ok(Event::Message(message)) => {
Some(EventKind::from_sse_bytes(&message.event, &message.data))
}
Err(err) => Some(Err(Error::SseClient(err))),
Err(err) => Some(Err(Error::SseClient(err.into()))),
}
})))
}

View File

@@ -10,7 +10,6 @@ use mediatype::{names, MediaType, MediaTypeList};
use multiaddr::Multiaddr;
use reqwest::header::HeaderMap;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use serde_utils::quoted_u64::Quoted;
use ssz::{Decode, DecodeError};
use ssz_derive::{Decode, Encode};
@@ -18,7 +17,9 @@ use std::fmt::{self, Display};
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use test_random_derive::TestRandom;
use types::beacon_block_body::KzgCommitments;
use types::test_utils::TestRandom;
pub use types::*;
#[cfg(feature = "lighthouse")]
@@ -687,7 +688,7 @@ pub struct ValidatorBalancesQuery {
pub id: Option<Vec<ValidatorId>>,
}
#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ValidatorBalancesRequestBody {
pub ids: Vec<ValidatorId>,
@@ -807,13 +808,13 @@ pub struct LightClientUpdatesQuery {
}
#[derive(Encode, Decode)]
pub struct LightClientUpdateSszResponse {
pub response_chunk_len: Vec<u8>,
pub response_chunk: Vec<u8>,
pub struct LightClientUpdateResponseChunk {
pub response_chunk_len: u64,
pub response_chunk: LightClientUpdateResponseChunkInner,
}
#[derive(Encode, Decode)]
pub struct LightClientUpdateResponseChunk {
pub struct LightClientUpdateResponseChunkInner {
pub context: [u8; 4],
pub payload: Vec<u8>,
}
@@ -1053,51 +1054,56 @@ pub struct SseExtendedPayloadAttributesGeneric<T> {
pub type SseExtendedPayloadAttributes = SseExtendedPayloadAttributesGeneric<SsePayloadAttributes>;
pub type VersionedSsePayloadAttributes = ForkVersionedResponse<SseExtendedPayloadAttributes>;
impl ForkVersionDeserialize for SsePayloadAttributes {
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
match fork_name {
ForkName::Bellatrix => serde_json::from_value(value)
.map(Self::V1)
.map_err(serde::de::Error::custom),
ForkName::Capella => serde_json::from_value(value)
.map(Self::V2)
.map_err(serde::de::Error::custom),
ForkName::Deneb => serde_json::from_value(value)
.map(Self::V3)
.map_err(serde::de::Error::custom),
ForkName::Electra => serde_json::from_value(value)
.map(Self::V3)
.map_err(serde::de::Error::custom),
ForkName::Fulu => serde_json::from_value(value)
.map(Self::V3)
.map_err(serde::de::Error::custom),
ForkName::Base | ForkName::Altair => Err(serde::de::Error::custom(format!(
"SsePayloadAttributes deserialization for {fork_name} not implemented"
))),
}
impl<'de> ContextDeserialize<'de, ForkName> for SsePayloadAttributes {
fn context_deserialize<D>(deserializer: D, context: ForkName) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let convert_err = |e| {
serde::de::Error::custom(format!(
"SsePayloadAttributes failed to deserialize: {:?}",
e
))
};
Ok(match context {
ForkName::Base | ForkName::Altair => {
return Err(serde::de::Error::custom(format!(
"SsePayloadAttributes failed to deserialize: unsupported fork '{}'",
context
)))
}
ForkName::Bellatrix => {
Self::V1(Deserialize::deserialize(deserializer).map_err(convert_err)?)
}
ForkName::Capella => {
Self::V2(Deserialize::deserialize(deserializer).map_err(convert_err)?)
}
ForkName::Deneb | ForkName::Electra | ForkName::Fulu => {
Self::V3(Deserialize::deserialize(deserializer).map_err(convert_err)?)
}
})
}
}
impl ForkVersionDeserialize for SseExtendedPayloadAttributes {
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
let helper: SseExtendedPayloadAttributesGeneric<serde_json::Value> =
serde_json::from_value(value).map_err(serde::de::Error::custom)?;
impl<'de> ContextDeserialize<'de, ForkName> for SseExtendedPayloadAttributes {
fn context_deserialize<D>(deserializer: D, context: ForkName) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let helper =
SseExtendedPayloadAttributesGeneric::<serde_json::Value>::deserialize(deserializer)?;
Ok(Self {
proposal_slot: helper.proposal_slot,
proposer_index: helper.proposer_index,
parent_block_root: helper.parent_block_root,
parent_block_number: helper.parent_block_number,
parent_block_hash: helper.parent_block_hash,
payload_attributes: SsePayloadAttributes::deserialize_by_fork::<D>(
payload_attributes: SsePayloadAttributes::context_deserialize(
helper.payload_attributes,
fork_name,
)?,
context,
)
.map_err(serde::de::Error::custom)?,
})
}
}
@@ -1115,8 +1121,8 @@ pub enum EventKind<E: EthSpec> {
ChainReorg(SseChainReorg),
ContributionAndProof(Box<SignedContributionAndProof<E>>),
LateHead(SseLateHead),
LightClientFinalityUpdate(Box<LightClientFinalityUpdate<E>>),
LightClientOptimisticUpdate(Box<LightClientOptimisticUpdate<E>>),
LightClientFinalityUpdate(Box<BeaconResponse<LightClientFinalityUpdate<E>>>),
LightClientOptimisticUpdate(Box<BeaconResponse<LightClientOptimisticUpdate<E>>>),
#[cfg(feature = "lighthouse")]
BlockReward(BlockReward),
PayloadAttributes(VersionedSsePayloadAttributes),
@@ -1198,22 +1204,24 @@ impl<E: EthSpec> EventKind<E> {
ServerError::InvalidServerSentEvent(format!("Payload Attributes: {:?}", e))
})?,
)),
"light_client_finality_update" => Ok(EventKind::LightClientFinalityUpdate(
serde_json::from_str(data).map_err(|e| {
"light_client_finality_update" => Ok(EventKind::LightClientFinalityUpdate(Box::new(
BeaconResponse::ForkVersioned(serde_json::from_str(data).map_err(|e| {
ServerError::InvalidServerSentEvent(format!(
"Light Client Finality Update: {:?}",
e
))
})?,
)),
"light_client_optimistic_update" => Ok(EventKind::LightClientOptimisticUpdate(
serde_json::from_str(data).map_err(|e| {
ServerError::InvalidServerSentEvent(format!(
"Light Client Optimistic Update: {:?}",
e
))
})?,
)),
})?),
))),
"light_client_optimistic_update" => {
Ok(EventKind::LightClientOptimisticUpdate(Box::new(
BeaconResponse::ForkVersioned(serde_json::from_str(data).map_err(|e| {
ServerError::InvalidServerSentEvent(format!(
"Light Client Optimistic Update: {:?}",
e
))
})?),
)))
}
#[cfg(feature = "lighthouse")]
"block_reward" => Ok(EventKind::BlockReward(serde_json::from_str(data).map_err(
|e| ServerError::InvalidServerSentEvent(format!("Block Reward: {:?}", e)),
@@ -1610,7 +1618,7 @@ mod tests {
}
}
#[derive(Debug, Encode, Serialize, Deserialize)]
#[derive(Debug, Encode, Serialize)]
#[serde(untagged)]
#[serde(bound = "E: EthSpec")]
#[ssz(enum_behaviour = "transparent")]
@@ -1623,7 +1631,7 @@ pub type JsonProduceBlockV3Response<E> =
ForkVersionedResponse<ProduceBlockV3Response<E>, ProduceBlockV3Metadata>;
/// A wrapper over a [`BeaconBlock`] or a [`BlockContents`].
#[derive(Debug, Encode, Serialize, Deserialize)]
#[derive(Debug, Encode, Serialize)]
#[serde(untagged)]
#[serde(bound = "E: EthSpec")]
#[ssz(enum_behaviour = "transparent")]
@@ -1737,18 +1745,18 @@ impl<E: EthSpec> FullBlockContents<E> {
}
}
impl<E: EthSpec> ForkVersionDeserialize for FullBlockContents<E> {
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
if fork_name.deneb_enabled() {
impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for FullBlockContents<E> {
fn context_deserialize<D>(deserializer: D, context: ForkName) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if context.deneb_enabled() {
Ok(FullBlockContents::BlockContents(
BlockContents::deserialize_by_fork::<'de, D>(value, fork_name)?,
BlockContents::context_deserialize::<D>(deserializer, context)?,
))
} else {
Ok(FullBlockContents::Block(
BeaconBlock::deserialize_by_fork::<'de, D>(value, fork_name)?,
BeaconBlock::context_deserialize::<D>(deserializer, context)?,
))
}
}
@@ -1815,7 +1823,7 @@ impl TryFrom<&HeaderMap> for ProduceBlockV3Metadata {
}
/// A wrapper over a [`SignedBeaconBlock`] or a [`SignedBlockContents`].
#[derive(Clone, Debug, Encode, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Encode, Serialize)]
#[serde(untagged)]
#[serde(bound = "E: EthSpec")]
#[ssz(enum_behaviour = "transparent")]
@@ -1824,6 +1832,26 @@ pub enum PublishBlockRequest<E: EthSpec> {
Block(Arc<SignedBeaconBlock<E>>),
}
impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for PublishBlockRequest<E> {
fn context_deserialize<D>(deserializer: D, context: ForkName) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value =
serde_json::Value::deserialize(deserializer).map_err(serde::de::Error::custom)?;
SignedBlockContents::<E>::context_deserialize(&value, context)
.map(PublishBlockRequest::BlockContents)
.or_else(|_| {
Arc::<SignedBeaconBlock<E>>::context_deserialize(&value, context)
.map(PublishBlockRequest::Block)
})
.map_err(|_| {
serde::de::Error::custom("could not match any variant of PublishBlockRequest")
})
}
}
impl<E: EthSpec> PublishBlockRequest<E> {
pub fn new(
block: Arc<SignedBeaconBlock<E>>,
@@ -1900,7 +1928,7 @@ impl<E: EthSpec> From<SignedBlockContentsTuple<E>> for PublishBlockRequest<E> {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Encode)]
#[derive(Debug, Clone, PartialEq, Serialize, Encode)]
#[serde(bound = "E: EthSpec")]
pub struct SignedBlockContents<E: EthSpec> {
pub signed_block: Arc<SignedBeaconBlock<E>>,
@@ -1909,7 +1937,33 @@ pub struct SignedBlockContents<E: EthSpec> {
pub blobs: BlobsList<E>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Encode)]
impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for SignedBlockContents<E> {
fn context_deserialize<D>(deserializer: D, context: ForkName) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(bound = "E: EthSpec")]
struct Helper<E: EthSpec> {
signed_block: serde_json::Value,
kzg_proofs: KzgProofs<E>,
#[serde(with = "ssz_types::serde_utils::list_of_hex_fixed_vec")]
blobs: BlobsList<E>,
}
let helper = Helper::<E>::deserialize(deserializer).map_err(serde::de::Error::custom)?;
let block = SignedBeaconBlock::context_deserialize(helper.signed_block, context)
.map_err(serde::de::Error::custom)?;
Ok(Self {
signed_block: Arc::new(block),
kzg_proofs: helper.kzg_proofs,
blobs: helper.blobs,
})
}
}
#[derive(Debug, Clone, Serialize, Encode)]
#[serde(bound = "E: EthSpec")]
pub struct BlockContents<E: EthSpec> {
pub block: BeaconBlock<E>,
@@ -1918,11 +1972,11 @@ pub struct BlockContents<E: EthSpec> {
pub blobs: BlobsList<E>,
}
impl<E: EthSpec> ForkVersionDeserialize for BlockContents<E> {
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for BlockContents<E> {
fn context_deserialize<D>(deserializer: D, context: ForkName) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(bound = "E: EthSpec")]
struct Helper<E: EthSpec> {
@@ -1931,10 +1985,13 @@ impl<E: EthSpec> ForkVersionDeserialize for BlockContents<E> {
#[serde(with = "ssz_types::serde_utils::list_of_hex_fixed_vec")]
blobs: BlobsList<E>,
}
let helper: Helper<E> = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
let helper = Helper::<E>::deserialize(deserializer).map_err(serde::de::Error::custom)?;
let block = BeaconBlock::context_deserialize(helper.block, context)
.map_err(serde::de::Error::custom)?;
Ok(Self {
block: BeaconBlock::deserialize_by_fork::<'de, D>(helper.block, fork_name)?,
block,
kzg_proofs: helper.kzg_proofs,
blobs: helper.blobs,
})
@@ -2006,22 +2063,22 @@ impl<E: EthSpec> FullPayloadContents<E> {
}
}
impl<E: EthSpec> ForkVersionDeserialize for FullPayloadContents<E> {
fn deserialize_by_fork<'de, D: Deserializer<'de>>(
value: Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
if fork_name.deneb_enabled() {
serde_json::from_value(value)
impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for FullPayloadContents<E> {
fn context_deserialize<D>(deserializer: D, context: ForkName) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if context.deneb_enabled() {
ExecutionPayloadAndBlobs::context_deserialize::<D>(deserializer, context)
.map(Self::PayloadAndBlobs)
.map_err(serde::de::Error::custom)
} else if fork_name.bellatrix_enabled() {
serde_json::from_value(value)
} else if context.bellatrix_enabled() {
ExecutionPayload::context_deserialize::<D>(deserializer, context)
.map(Self::Payload)
.map_err(serde::de::Error::custom)
} else {
Err(serde::de::Error::custom(format!(
"FullPayloadContents deserialization for {fork_name} not implemented"
"FullPayloadContents deserialization for {context} not implemented"
)))
}
}
@@ -2034,6 +2091,30 @@ pub struct ExecutionPayloadAndBlobs<E: EthSpec> {
pub blobs_bundle: BlobsBundle<E>,
}
impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for ExecutionPayloadAndBlobs<E> {
fn context_deserialize<D>(deserializer: D, context: ForkName) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(bound = "E: EthSpec")]
struct Helper<E: EthSpec> {
execution_payload: serde_json::Value,
blobs_bundle: BlobsBundle<E>,
}
let helper = Helper::<E>::deserialize(deserializer).map_err(serde::de::Error::custom)?;
Ok(Self {
execution_payload: ExecutionPayload::context_deserialize(
helper.execution_payload,
context,
)
.map_err(serde::de::Error::custom)?,
blobs_bundle: helper.blobs_bundle,
})
}
}
impl<E: EthSpec> ForkVersionDecode for ExecutionPayloadAndBlobs<E> {
fn from_ssz_bytes_by_fork(bytes: &[u8], fork_name: ForkName) -> Result<Self, DecodeError> {
let mut builder = ssz::SszDecoderBuilder::new(bytes);
@@ -2064,7 +2145,7 @@ pub enum ContentType {
Ssz,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, Encode, Decode)]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom)]
#[serde(bound = "E: EthSpec")]
pub struct BlobsBundle<E: EthSpec> {
pub commitments: KzgCommitments<E>,
@@ -2159,6 +2240,10 @@ pub struct StandardAttestationRewards {
#[cfg(test)]
mod test {
use std::fmt::Debug;
use types::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use super::*;
#[test]
@@ -2172,4 +2257,173 @@ mod test {
let y: ValidatorId = serde_json::from_str(pubkey_str).unwrap();
assert_eq!(serde_json::to_string(&y).unwrap(), pubkey_str);
}
#[test]
fn test_publish_block_request_context_deserialize() {
let round_trip_test = |request: PublishBlockRequest<MainnetEthSpec>| {
let fork_name = request.signed_block().fork_name_unchecked();
let json_str = serde_json::to_string(&request).unwrap();
let mut de = serde_json::Deserializer::from_str(&json_str);
let deserialized_request =
PublishBlockRequest::<MainnetEthSpec>::context_deserialize(&mut de, fork_name)
.unwrap();
assert_eq!(request, deserialized_request);
};
let rng = &mut XorShiftRng::from_seed([42; 16]);
for fork_name in ForkName::list_all() {
let signed_beacon_block =
map_fork_name!(fork_name, SignedBeaconBlock, <_>::random_for_test(rng));
let request = if fork_name.deneb_enabled() {
let kzg_proofs = KzgProofs::<MainnetEthSpec>::random_for_test(rng);
let blobs = BlobsList::<MainnetEthSpec>::random_for_test(rng);
let block_contents = SignedBlockContents {
signed_block: Arc::new(signed_beacon_block),
kzg_proofs,
blobs,
};
PublishBlockRequest::BlockContents(block_contents)
} else {
PublishBlockRequest::Block(Arc::new(signed_beacon_block))
};
round_trip_test(request);
println!("fork_name: {:?} PASSED", fork_name);
}
}
#[test]
fn test_signed_block_contents_context_deserialize() {
let round_trip_test = |contents: SignedBlockContents<MainnetEthSpec>| {
let fork_name = contents.signed_block.fork_name_unchecked();
let json_str = serde_json::to_string(&contents).unwrap();
let mut de = serde_json::Deserializer::from_str(&json_str);
let deserialized_contents =
SignedBlockContents::<MainnetEthSpec>::context_deserialize(&mut de, fork_name)
.unwrap();
assert_eq!(contents, deserialized_contents);
};
let mut fork_name = ForkName::Deneb;
let rng = &mut XorShiftRng::from_seed([42; 16]);
loop {
let signed_beacon_block =
map_fork_name!(fork_name, SignedBeaconBlock, <_>::random_for_test(rng));
let kzg_proofs = KzgProofs::<MainnetEthSpec>::random_for_test(rng);
let blobs = BlobsList::<MainnetEthSpec>::random_for_test(rng);
let block_contents = SignedBlockContents {
signed_block: Arc::new(signed_beacon_block),
kzg_proofs,
blobs,
};
round_trip_test(block_contents);
println!("fork_name: {:?} PASSED", fork_name);
if let Some(next_fork_name) = fork_name.next_fork() {
fork_name = next_fork_name;
} else {
break;
}
}
}
#[test]
fn test_execution_payload_execution_payload_deserialize_by_fork() {
let rng = &mut XorShiftRng::from_seed([42; 16]);
let payloads = [
ExecutionPayload::Bellatrix(
ExecutionPayloadBellatrix::<MainnetEthSpec>::random_for_test(rng),
),
ExecutionPayload::Capella(ExecutionPayloadCapella::<MainnetEthSpec>::random_for_test(
rng,
)),
ExecutionPayload::Deneb(ExecutionPayloadDeneb::<MainnetEthSpec>::random_for_test(
rng,
)),
ExecutionPayload::Electra(ExecutionPayloadElectra::<MainnetEthSpec>::random_for_test(
rng,
)),
ExecutionPayload::Fulu(ExecutionPayloadFulu::<MainnetEthSpec>::random_for_test(rng)),
];
let merged_forks = &ForkName::list_all()[2..];
assert_eq!(
payloads.len(),
merged_forks.len(),
"we should test every known fork; add new fork variant to payloads above"
);
for (payload, &fork_name) in payloads.into_iter().zip(merged_forks) {
assert_eq!(payload.fork_name(), fork_name);
let payload_str = serde_json::to_string(&payload).unwrap();
let mut de = serde_json::Deserializer::from_str(&payload_str);
generic_deserialize_by_fork(&mut de, payload, fork_name);
}
}
#[test]
fn test_execution_payload_and_blobs_deserialize_by_fork() {
let rng = &mut XorShiftRng::from_seed([42; 16]);
let payloads = [
{
let execution_payload =
ExecutionPayload::Deneb(
ExecutionPayloadDeneb::<MainnetEthSpec>::random_for_test(rng),
);
let blobs_bundle = BlobsBundle::random_for_test(rng);
ExecutionPayloadAndBlobs {
execution_payload,
blobs_bundle,
}
},
{
let execution_payload =
ExecutionPayload::Electra(
ExecutionPayloadElectra::<MainnetEthSpec>::random_for_test(rng),
);
let blobs_bundle = BlobsBundle::random_for_test(rng);
ExecutionPayloadAndBlobs {
execution_payload,
blobs_bundle,
}
},
{
let execution_payload =
ExecutionPayload::Fulu(
ExecutionPayloadFulu::<MainnetEthSpec>::random_for_test(rng),
);
let blobs_bundle = BlobsBundle::random_for_test(rng);
ExecutionPayloadAndBlobs {
execution_payload,
blobs_bundle,
}
},
];
let blob_forks = &ForkName::list_all()[4..];
assert_eq!(
payloads.len(),
blob_forks.len(),
"we should test every known fork; add new fork variant to payloads above"
);
for (payload, &fork_name) in payloads.into_iter().zip(blob_forks) {
assert_eq!(payload.execution_payload.fork_name(), fork_name);
let payload_str = serde_json::to_string(&payload).unwrap();
let mut de = serde_json::Deserializer::from_str(&payload_str);
generic_deserialize_by_fork(&mut de, payload, fork_name);
}
}
fn generic_deserialize_by_fork<
'de,
D: Deserializer<'de>,
O: ContextDeserialize<'de, ForkName> + PartialEq + Debug,
>(
deserializer: D,
original: O,
fork_name: ForkName,
) {
let roundtrip = O::context_deserialize::<D>(deserializer, fork_name).unwrap();
assert_eq!(original, roundtrip);
}
}