Properly Deserialize ForkVersionedResponses (#3944)

* Move ForkVersionedResponse to consensus/types

* Properly Deserialize ForkVersionedResponses

* Elide Types in from_value Calls

* Added Tests for ForkVersionedResponse Deserialize

* Address Sean's Comments & Make Less Restrictive

* Utilize `map_fork_name!`
This commit is contained in:
ethDreamer
2023-02-10 08:49:25 -06:00
committed by GitHub
parent e062a7cf76
commit 39f8327f73
12 changed files with 335 additions and 87 deletions

View File

@@ -685,6 +685,24 @@ impl<E: EthSpec> From<BeaconBlock<E, FullPayload<E>>>
}
}
impl<T: EthSpec, Payload: AbstractExecPayload<T>> ForkVersionDeserialize
for BeaconBlock<T, Payload>
{
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
Ok(map_fork_name!(
fork_name,
Self,
serde_json::from_value(value).map_err(|e| serde::de::Error::custom(format!(
"BeaconBlock failed to deserialize: {:?}",
e
)))?
))
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -1853,3 +1853,19 @@ impl<T: EthSpec> CompareFields for BeaconState<T> {
}
}
}
impl<T: EthSpec> ForkVersionDeserialize for BeaconState<T> {
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
Ok(map_fork_name!(
fork_name,
Self,
serde_json::from_value(value).map_err(|e| serde::de::Error::custom(format!(
"BeaconState failed to deserialize: {:?}",
e
)))?
))
}
}

View File

@@ -1,6 +1,6 @@
use crate::{
AbstractExecPayload, ChainSpec, EthSpec, ExecPayload, ExecutionPayloadHeader, SignedRoot,
Uint256,
AbstractExecPayload, ChainSpec, EthSpec, ExecPayload, ExecutionPayloadHeader, ForkName,
ForkVersionDeserialize, SignedRoot, Uint256,
};
use bls::PublicKeyBytes;
use bls::Signature;
@@ -34,6 +34,60 @@ pub struct SignedBuilderBid<E: EthSpec, Payload: AbstractExecPayload<E>> {
pub signature: Signature,
}
impl<T: EthSpec, Payload: AbstractExecPayload<T>> ForkVersionDeserialize
for BuilderBid<T, Payload>
{
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
let convert_err = |_| {
serde::de::Error::custom(
"BuilderBid failed to deserialize: unable to convert payload header to payload",
)
};
#[derive(Deserialize)]
struct Helper {
header: serde_json::Value,
#[serde(with = "eth2_serde_utils::quoted_u256")]
value: Uint256,
pubkey: PublicKeyBytes,
}
let helper: Helper = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
let payload_header =
ExecutionPayloadHeader::deserialize_by_fork::<'de, D>(helper.header, fork_name)?;
Ok(Self {
header: Payload::try_from(payload_header).map_err(convert_err)?,
value: helper.value,
pubkey: helper.pubkey,
_phantom_data: Default::default(),
})
}
}
impl<T: EthSpec, Payload: AbstractExecPayload<T>> ForkVersionDeserialize
for SignedBuilderBid<T, Payload>
{
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
#[derive(Deserialize)]
struct Helper {
pub message: serde_json::Value,
pub signature: Signature,
}
let helper: Helper = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
Ok(Self {
message: BuilderBid::deserialize_by_fork::<'de, D>(helper.message, fork_name)?,
signature: helper.signature,
})
}
}
struct BlindedPayloadAsHeader<E>(PhantomData<E>);
impl<E: EthSpec, Payload: ExecPayload<E>> SerializeAs<Payload> for BlindedPayloadAsHeader<E> {

View File

@@ -146,3 +146,26 @@ impl<T: EthSpec> ExecutionPayload<T> {
+ (T::max_withdrawals_per_payload() * <Withdrawal as Encode>::ssz_fixed_len())
}
}
impl<T: EthSpec> ForkVersionDeserialize for ExecutionPayload<T> {
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
let convert_err = |e| {
serde::de::Error::custom(format!("ExecutionPayload failed to deserialize: {:?}", e))
};
Ok(match fork_name {
ForkName::Merge => Self::Merge(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Capella => Self::Capella(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Eip4844 => Self::Eip4844(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Base | ForkName::Altair => {
return Err(serde::de::Error::custom(format!(
"ExecutionPayload failed to deserialize: unsupported fork '{}'",
fork_name
)));
}
})
}
}

View File

@@ -282,3 +282,29 @@ impl<T: EthSpec> TryFrom<ExecutionPayloadHeader<T>> for ExecutionPayloadHeaderEi
}
}
}
impl<T: EthSpec> ForkVersionDeserialize for ExecutionPayloadHeader<T> {
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
let convert_err = |e| {
serde::de::Error::custom(format!(
"ExecutionPayloadHeader failed to deserialize: {:?}",
e
))
};
Ok(match fork_name {
ForkName::Merge => Self::Merge(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Capella => Self::Capella(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Eip4844 => Self::Eip4844(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Base | ForkName::Altair => {
return Err(serde::de::Error::custom(format!(
"ExecutionPayloadHeader failed to deserialize: unsupported fork '{}'",
fork_name
)));
}
})
}
}

View File

@@ -0,0 +1,138 @@
use crate::ForkName;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::value::Value;
use std::sync::Arc;
// Deserialize is only implemented for types that implement ForkVersionDeserialize
#[derive(Debug, PartialEq, Clone, Serialize)]
pub struct ExecutionOptimisticForkVersionedResponse<T> {
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<ForkName>,
pub execution_optimistic: Option<bool>,
pub data: T,
}
impl<'de, F> serde::Deserialize<'de> for ExecutionOptimisticForkVersionedResponse<F>
where
F: ForkVersionDeserialize,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Helper {
version: Option<ForkName>,
execution_optimistic: Option<bool>,
data: serde_json::Value,
}
let helper = Helper::deserialize(deserializer)?;
let data = match helper.version {
Some(fork_name) => F::deserialize_by_fork::<'de, D>(helper.data, fork_name)?,
None => serde_json::from_value(helper.data).map_err(serde::de::Error::custom)?,
};
Ok(ExecutionOptimisticForkVersionedResponse {
version: helper.version,
execution_optimistic: helper.execution_optimistic,
data,
})
}
}
pub trait ForkVersionDeserialize: Sized + DeserializeOwned {
fn deserialize_by_fork<'de, D: Deserializer<'de>>(
value: Value,
fork_name: ForkName,
) -> Result<Self, D::Error>;
}
// Deserialize is only implemented for types that implement ForkVersionDeserialize
#[derive(Debug, PartialEq, Clone, Serialize)]
pub struct ForkVersionedResponse<T> {
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<ForkName>,
pub data: T,
}
impl<'de, F> serde::Deserialize<'de> for ForkVersionedResponse<F>
where
F: ForkVersionDeserialize,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Helper {
version: Option<ForkName>,
data: serde_json::Value,
}
let helper = Helper::deserialize(deserializer)?;
let data = match helper.version {
Some(fork_name) => F::deserialize_by_fork::<'de, D>(helper.data, fork_name)?,
None => serde_json::from_value(helper.data).map_err(serde::de::Error::custom)?,
};
Ok(ForkVersionedResponse {
version: helper.version,
data,
})
}
}
impl<F: ForkVersionDeserialize> ForkVersionDeserialize for Arc<F> {
fn deserialize_by_fork<'de, D: Deserializer<'de>>(
value: Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
Ok(Arc::new(F::deserialize_by_fork::<'de, D>(
value, fork_name,
)?))
}
}
#[cfg(test)]
mod fork_version_response_tests {
use crate::{
ExecutionPayload, ExecutionPayloadMerge, ForkName, ForkVersionedResponse, MainnetEthSpec,
};
use serde_json::json;
#[test]
fn fork_versioned_response_deserialize_correct_fork() {
type E = MainnetEthSpec;
let response_json =
serde_json::to_string(&json!(ForkVersionedResponse::<ExecutionPayload<E>> {
version: Some(ForkName::Merge),
data: ExecutionPayload::Merge(ExecutionPayloadMerge::default()),
}))
.unwrap();
let result: Result<ForkVersionedResponse<ExecutionPayload<E>>, _> =
serde_json::from_str(&response_json);
assert!(result.is_ok());
}
#[test]
fn fork_versioned_response_deserialize_incorrect_fork() {
type E = MainnetEthSpec;
let response_json =
serde_json::to_string(&json!(ForkVersionedResponse::<ExecutionPayload<E>> {
version: Some(ForkName::Capella),
data: ExecutionPayload::Merge(ExecutionPayloadMerge::default()),
}))
.unwrap();
let result: Result<ForkVersionedResponse<ExecutionPayload<E>>, _> =
serde_json::from_str(&response_json);
assert!(result.is_err());
}
}

View File

@@ -46,6 +46,7 @@ pub mod execution_payload_header;
pub mod fork;
pub mod fork_data;
pub mod fork_name;
pub mod fork_versioned_response;
pub mod free_attestation;
pub mod graffiti;
pub mod historical_batch;
@@ -150,6 +151,9 @@ pub use crate::fork::Fork;
pub use crate::fork_context::ForkContext;
pub use crate::fork_data::ForkData;
pub use crate::fork_name::{ForkName, InconsistentFork};
pub use crate::fork_versioned_response::{
ExecutionOptimisticForkVersionedResponse, ForkVersionDeserialize, ForkVersionedResponse,
};
pub use crate::free_attestation::FreeAttestation;
pub use crate::graffiti::{Graffiti, GRAFFITI_BYTES_LEN};
pub use crate::historical_batch::HistoricalBatch;

View File

@@ -487,6 +487,24 @@ impl<E: EthSpec> SignedBeaconBlock<E> {
}
}
impl<E: EthSpec, Payload: AbstractExecPayload<E>> ForkVersionDeserialize
for SignedBeaconBlock<E, Payload>
{
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
Ok(map_fork_name!(
fork_name,
Self,
serde_json::from_value(value).map_err(|e| serde::de::Error::custom(format!(
"SignedBeaconBlock failed to deserialize: {:?}",
e
)))?
))
}
}
#[cfg(test)]
mod test {
use super::*;