Fulu EF tests v1.6.0-alpha.0 (#7540)

Update to EF tests v1.6.0-alpha.0
This commit is contained in:
ethDreamer
2025-06-04 01:34:12 -05:00
committed by GitHub
parent 357a8ccbb9
commit 2d9fc34d43
21 changed files with 494 additions and 167 deletions

View File

@@ -1,8 +1,11 @@
use serde::Deserialize;
use context_deserialize::ContextDeserialize;
use serde::{Deserialize, Deserializer};
use ssz::Encode;
use ssz_derive::{Decode, Encode};
use std::fmt::Debug;
use types::ForkName;
use std::marker::PhantomData;
use tree_hash::TreeHash;
use types::{DataColumnsByRootIdentifier, EthSpec, ForkName, Hash256};
/// Macro to wrap U128 and U256 so they deserialize correctly.
macro_rules! uint_wrapper {
@@ -40,6 +43,15 @@ macro_rules! uint_wrapper {
self.x.tree_hash_root()
}
}
impl<'de, T> ContextDeserialize<'de, T> for $wrapper_name {
fn context_deserialize<D>(deserializer: D, _context: T) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
<$wrapper_name>::deserialize(deserializer)
}
}
};
}
@@ -47,26 +59,63 @@ uint_wrapper!(DecimalU128, alloy_primitives::U128);
uint_wrapper!(DecimalU256, alloy_primitives::U256);
/// Trait for types that can be used in SSZ static tests.
pub trait SszStaticType:
serde::de::DeserializeOwned + Encode + Clone + PartialEq + Debug + Sync
{
pub trait SszStaticType: Encode + Clone + PartialEq + Debug + Sync {}
impl<T> SszStaticType for T where T: Encode + Clone + PartialEq + Debug + Sync {}
/// We need the `EthSpec` to implement `LoadCase` for this type, in order to work out the
/// ChainSpec.
///
/// No other type currently requires this kind of context.
#[derive(Debug, Encode, Clone, PartialEq)]
#[ssz(struct_behaviour = "transparent")]
pub struct DataColumnsByRootIdentifierWrapper<E: EthSpec> {
pub value: DataColumnsByRootIdentifier,
// SSZ derive is a bit buggy and requires skip_deserializing for transparent to work.
#[ssz(skip_serializing, skip_deserializing)]
pub _phantom: PhantomData<E>,
}
impl<T> SszStaticType for T where
T: serde::de::DeserializeOwned + Encode + Clone + PartialEq + Debug + Sync
impl<'de, E: EthSpec> ContextDeserialize<'de, (ForkName, usize)>
for DataColumnsByRootIdentifierWrapper<E>
{
fn context_deserialize<D>(deserializer: D, context: (ForkName, usize)) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = DataColumnsByRootIdentifier::context_deserialize(deserializer, context)?;
Ok(DataColumnsByRootIdentifierWrapper {
value,
_phantom: PhantomData,
})
}
}
/// Return the fork immediately prior to a fork.
pub fn previous_fork(fork_name: ForkName) -> ForkName {
match fork_name {
ForkName::Base => ForkName::Base,
ForkName::Altair => ForkName::Base,
ForkName::Bellatrix => ForkName::Altair,
ForkName::Capella => ForkName::Bellatrix,
ForkName::Deneb => ForkName::Capella,
ForkName::Electra => ForkName::Deneb,
ForkName::Fulu => ForkName::Electra,
// We can delete this if we ever get `tree_hash(struct_behaviour = "transparent")`.
impl<E: EthSpec> TreeHash for DataColumnsByRootIdentifierWrapper<E> {
fn tree_hash_type() -> tree_hash::TreeHashType {
DataColumnsByRootIdentifier::tree_hash_type()
}
fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding {
self.value.tree_hash_packed_encoding()
}
fn tree_hash_packing_factor() -> usize {
DataColumnsByRootIdentifier::tree_hash_packing_factor()
}
fn tree_hash_root(&self) -> Hash256 {
self.value.tree_hash_root()
}
}
impl<E: EthSpec> From<DataColumnsByRootIdentifier> for DataColumnsByRootIdentifierWrapper<E> {
fn from(value: DataColumnsByRootIdentifier) -> Self {
Self {
value,
_phantom: PhantomData,
}
}
}

View File

@@ -1,6 +1,5 @@
use super::*;
use crate::case_result::compare_beacon_state_results_without_caches;
use crate::cases::common::previous_fork;
use crate::decode::{ssz_decode_state, yaml_decode_file};
use serde::Deserialize;
use state_processing::upgrade::{
@@ -33,7 +32,10 @@ impl<E: EthSpec> LoadCase for ForkTest<E> {
assert_eq!(metadata.fork_name(), fork_name);
// Decode pre-state with previous fork.
let pre_spec = &previous_fork(fork_name).make_genesis_spec(E::default_spec());
let pre_spec = &fork_name
.previous_fork()
.unwrap_or(ForkName::Base)
.make_genesis_spec(E::default_spec());
let pre = ssz_decode_state(&path.join("pre.ssz_snappy"), pre_spec)?;
// Decode post-state with target fork.

View File

@@ -0,0 +1,54 @@
use super::*;
use crate::case_result::compare_result;
use kzg::Cell;
use serde::Deserialize;
use std::marker::PhantomData;
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct KZGComputeCellsInput {
pub blob: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(bound = "E: EthSpec", deny_unknown_fields)]
pub struct KZGComputeCells<E: EthSpec> {
pub input: KZGComputeCellsInput,
pub output: Option<Vec<String>>,
#[serde(skip)]
_phantom: PhantomData<E>,
}
impl<E: EthSpec> LoadCase for KZGComputeCells<E> {
fn load_from_dir(path: &Path, _fork_name: ForkName) -> Result<Self, Error> {
decode::yaml_decode_file(path.join("data.yaml").as_path())
}
}
impl<E: EthSpec> Case for KZGComputeCells<E> {
fn is_enabled_for_fork(fork_name: ForkName) -> bool {
fork_name.fulu_enabled()
}
fn result(&self, _case_index: usize, _fork_name: ForkName) -> Result<(), Error> {
let cells = parse_blob::<E>(&self.input.blob)
.and_then(|blob| {
let blob = blob.as_ref().try_into().map_err(|e| {
Error::InternalError(format!("Failed to convert blob to kzg blob: {e:?}"))
})?;
let kzg = get_kzg();
kzg.compute_cells(blob).map_err(|e| {
Error::InternalError(format!("Failed to compute cells and kzg proofs: {e:?}"))
})
})
.map(|cells| cells.to_vec());
let expected = self.output.as_ref().map(|cells| {
parse_cells_and_proofs(cells, &[])
.map(|(cells, _)| cells)
.expect("Valid cells")
});
compare_result::<Vec<Cell>, _>(&cells, &expected)
}
}

View File

@@ -22,10 +22,11 @@ use state_processing::{
ConsensusContext,
};
use std::fmt::Debug;
use std::path::PathBuf;
use types::{
Attestation, AttesterSlashing, BeaconBlock, BeaconBlockBody, BeaconBlockBodyBellatrix,
BeaconBlockBodyCapella, BeaconBlockBodyDeneb, BeaconBlockBodyElectra, BeaconState,
BlindedPayload, ConsolidationRequest, Deposit, DepositRequest, ExecutionPayload,
BeaconBlockBodyCapella, BeaconBlockBodyDeneb, BeaconBlockBodyElectra, BeaconBlockBodyFulu,
BeaconState, BlindedPayload, ConsolidationRequest, Deposit, DepositRequest, ExecutionPayload,
ForkVersionDecode, FullPayload, ProposerSlashing, SignedBlsToExecutionChange,
SignedVoluntaryExit, SyncAggregate, WithdrawalRequest,
};
@@ -49,6 +50,7 @@ pub struct WithdrawalsPayload<E: EthSpec> {
#[derive(Debug, Clone)]
pub struct Operations<E: EthSpec, O: Operation<E>> {
path: PathBuf,
metadata: Metadata,
execution_metadata: Option<ExecutionMetadata>,
pub pre: BeaconState<E>,
@@ -357,8 +359,8 @@ impl<E: EthSpec> Operation<E> for BeaconBlockBody<E, BlindedPayload<E>> {
BeaconBlockBody::Electra(inner.clone_as_blinded())
}
ForkName::Fulu => {
let inner = <BeaconBlockBodyElectra<E, FullPayload<E>>>::from_ssz_bytes(bytes)?;
BeaconBlockBody::Electra(inner.clone_as_blinded())
let inner = <BeaconBlockBodyFulu<E, FullPayload<E>>>::from_ssz_bytes(bytes)?;
BeaconBlockBody::Fulu(inner.clone_as_blinded())
}
_ => panic!(),
})
@@ -555,6 +557,7 @@ impl<E: EthSpec, O: Operation<E>> LoadCase for Operations<E, O> {
};
Ok(Self {
path: path.into(),
metadata,
execution_metadata,
pre,
@@ -574,6 +577,17 @@ impl<E: EthSpec, O: Operation<E>> Case for Operations<E, O> {
}
fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> {
// FIXME(das): remove this once v1.6.0-alpha.1 is released
// We are ahead of the v1.6.0-alpha.0 spec in our implementation of
// `get_max_blobs_per_block`, so we fail the execution payload test which expects the
// empty blob schedule to generate an error.
if O::handler_name() == "execution_payload"
&& fork_name == ForkName::Fulu
&& self.path.ends_with("invalid_exceed_max_blobs_per_block")
{
return Err(Error::SkippedKnownFailure);
}
let spec = &testing_spec::<E>(fork_name);
let mut pre_state = self.pre.clone();

View File

@@ -3,7 +3,9 @@
use super::*;
use crate::cases::common::{DecimalU128, DecimalU256, SszStaticType};
use crate::cases::ssz_static::{check_serialization, check_tree_hash};
use crate::decode::{log_file_access, snappy_decode_file, yaml_decode_file};
use crate::decode::{context_yaml_decode_file, log_file_access, snappy_decode_file};
use context_deserialize::ContextDeserialize;
use context_deserialize_derive::context_deserialize;
use serde::{de::Error as SerdeError, Deserialize, Deserializer};
use ssz_derive::{Decode, Encode};
use tree_hash::TreeHash;
@@ -12,6 +14,7 @@ use types::typenum::*;
use types::{BitList, BitVector, FixedVector, ForkName, VariableList, Vector};
#[derive(Debug, Clone, Deserialize)]
#[context_deserialize(ForkName)]
struct Metadata {
root: String,
#[serde(rename(deserialize = "signing_root"))]
@@ -118,7 +121,7 @@ macro_rules! type_dispatch {
}
impl Case for SszGeneric {
fn result(&self, _case_index: usize, _fork_name: ForkName) -> Result<(), Error> {
fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> {
let parts = self.case_name.split('_').collect::<Vec<_>>();
match self.handler_name.as_str() {
@@ -134,7 +137,7 @@ impl Case for SszGeneric {
type_dispatch!(
ssz_generic_test,
(&self.path),
(&self.path, fork_name),
Vector,
<>,
[elem_ty => primitive_type]
@@ -142,7 +145,7 @@ impl Case for SszGeneric {
)?;
type_dispatch!(
ssz_generic_test,
(&self.path),
(&self.path, fork_name),
FixedVector,
<>,
[elem_ty => primitive_type]
@@ -159,7 +162,7 @@ impl Case for SszGeneric {
type_dispatch!(
ssz_generic_test,
(&self.path),
(&self.path, fork_name),
BitList,
<>,
[limit => typenum]
@@ -170,21 +173,21 @@ impl Case for SszGeneric {
type_dispatch!(
ssz_generic_test,
(&self.path),
(&self.path, fork_name),
BitVector,
<>,
[length => typenum]
)?;
}
"boolean" => {
ssz_generic_test::<bool>(&self.path)?;
ssz_generic_test::<bool>(&self.path, fork_name)?;
}
"uints" => {
let type_name = "uint".to_owned() + parts[1];
type_dispatch!(
ssz_generic_test,
(&self.path),
(&self.path, fork_name),
_,
<>,
[type_name.as_str() => primitive_type]
@@ -195,7 +198,7 @@ impl Case for SszGeneric {
type_dispatch!(
ssz_generic_test,
(&self.path),
(&self.path, fork_name),
_,
<>,
[type_name => test_container]
@@ -207,10 +210,15 @@ impl Case for SszGeneric {
}
}
fn ssz_generic_test<T: SszStaticType + TreeHash + ssz::Decode>(path: &Path) -> Result<(), Error> {
fn ssz_generic_test<
T: SszStaticType + for<'de> ContextDeserialize<'de, ForkName> + TreeHash + ssz::Decode,
>(
path: &Path,
fork_name: ForkName,
) -> Result<(), Error> {
let meta_path = path.join("meta.yaml");
let meta: Option<Metadata> = if meta_path.is_file() {
Some(yaml_decode_file(&meta_path)?)
Some(context_yaml_decode_file(&meta_path, fork_name)?)
} else {
None
};
@@ -220,7 +228,7 @@ fn ssz_generic_test<T: SszStaticType + TreeHash + ssz::Decode>(path: &Path) -> R
let value_path = path.join("value.yaml");
let value: Option<T> = if value_path.is_file() {
Some(yaml_decode_file(&value_path)?)
Some(context_yaml_decode_file(&value_path, fork_name)?)
} else {
None
};
@@ -246,17 +254,20 @@ fn ssz_generic_test<T: SszStaticType + TreeHash + ssz::Decode>(path: &Path) -> R
// Containers for SSZ generic tests
#[derive(Debug, Clone, Default, PartialEq, Decode, Encode, TreeHash, Deserialize)]
#[context_deserialize(ForkName)]
struct SingleFieldTestStruct {
A: u8,
}
#[derive(Debug, Clone, Default, PartialEq, Decode, Encode, TreeHash, Deserialize)]
#[context_deserialize(ForkName)]
struct SmallTestStruct {
A: u16,
B: u16,
}
#[derive(Debug, Clone, Default, PartialEq, Decode, Encode, TreeHash, Deserialize)]
#[context_deserialize(ForkName)]
struct FixedTestStruct {
A: u8,
B: u64,
@@ -264,6 +275,7 @@ struct FixedTestStruct {
}
#[derive(Debug, Clone, Default, PartialEq, Decode, Encode, TreeHash, Deserialize)]
#[context_deserialize(ForkName)]
struct VarTestStruct {
A: u16,
B: VariableList<u16, U1024>,
@@ -271,6 +283,7 @@ struct VarTestStruct {
}
#[derive(Debug, Clone, Default, PartialEq, Decode, Encode, TreeHash, Deserialize)]
#[context_deserialize(ForkName)]
struct ComplexTestStruct {
A: u16,
B: VariableList<u16, U128>,
@@ -283,6 +296,7 @@ struct ComplexTestStruct {
}
#[derive(Debug, Clone, PartialEq, Decode, Encode, TreeHash, Deserialize)]
#[context_deserialize(ForkName)]
struct BitsStruct {
A: BitList<U5>,
B: BitVector<U2>,

View File

@@ -1,10 +1,12 @@
use super::*;
use crate::case_result::compare_result;
use crate::decode::{snappy_decode_file, yaml_decode_file};
use crate::cases::common::DataColumnsByRootIdentifierWrapper;
use crate::decode::{context_yaml_decode_file, snappy_decode_file, yaml_decode_file};
use context_deserialize::ContextDeserialize;
use serde::Deserialize;
use ssz::Decode;
use tree_hash::TreeHash;
use types::{BeaconBlock, BeaconState, Hash256, SignedBeaconBlock};
use types::{BeaconBlock, BeaconState, DataColumnsByRootIdentifier, Hash256, SignedBeaconBlock};
#[derive(Debug, Clone, Deserialize)]
struct SszStaticRoots {
@@ -37,18 +39,28 @@ pub struct SszStaticWithSpec<T> {
value: T,
}
fn load_from_dir<T: SszStaticType>(path: &Path) -> Result<(SszStaticRoots, Vec<u8>, T), Error> {
fn load_from_dir<T: SszStaticType + for<'de> ContextDeserialize<'de, ForkName>>(
path: &Path,
fork_name: ForkName,
) -> Result<(SszStaticRoots, Vec<u8>, T), Error> {
load_from_dir_with_context(path, fork_name)
}
fn load_from_dir_with_context<T: SszStaticType + for<'de> ContextDeserialize<'de, C>, C>(
path: &Path,
context: C,
) -> Result<(SszStaticRoots, Vec<u8>, T), Error> {
let roots = yaml_decode_file(&path.join("roots.yaml"))?;
let serialized = snappy_decode_file(&path.join("serialized.ssz_snappy"))
.expect("serialized.ssz_snappy exists");
let value = yaml_decode_file(&path.join("value.yaml"))?;
let value = context_yaml_decode_file(&path.join("value.yaml"), context)?;
Ok((roots, serialized, value))
}
impl<T: SszStaticType> LoadCase for SszStatic<T> {
fn load_from_dir(path: &Path, _fork_name: ForkName) -> Result<Self, Error> {
load_from_dir(path).map(|(roots, serialized, value)| Self {
impl<T: SszStaticType + for<'de> ContextDeserialize<'de, ForkName>> LoadCase for SszStatic<T> {
fn load_from_dir(path: &Path, fork_name: ForkName) -> Result<Self, Error> {
load_from_dir(path, fork_name).map(|(roots, serialized, value)| Self {
roots,
serialized,
value,
@@ -56,19 +68,9 @@ impl<T: SszStaticType> LoadCase for SszStatic<T> {
}
}
impl<T: SszStaticType> LoadCase for SszStaticTHC<T> {
fn load_from_dir(path: &Path, _fork_name: ForkName) -> Result<Self, Error> {
load_from_dir(path).map(|(roots, serialized, value)| Self {
roots,
serialized,
value,
})
}
}
impl<T: SszStaticType> LoadCase for SszStaticWithSpec<T> {
fn load_from_dir(path: &Path, _fork_name: ForkName) -> Result<Self, Error> {
load_from_dir(path).map(|(roots, serialized, value)| Self {
impl<T: SszStaticType + for<'de> ContextDeserialize<'de, ForkName>> LoadCase for SszStaticTHC<T> {
fn load_from_dir(path: &Path, fork_name: ForkName) -> Result<Self, Error> {
load_from_dir(path, fork_name).map(|(roots, serialized, value)| Self {
roots,
serialized,
value,
@@ -124,6 +126,16 @@ impl<E: EthSpec> Case for SszStaticTHC<BeaconState<E>> {
}
}
impl<E: EthSpec> LoadCase for SszStaticWithSpec<BeaconBlock<E>> {
fn load_from_dir(path: &Path, fork_name: ForkName) -> Result<Self, Error> {
load_from_dir(path, fork_name).map(|(roots, serialized, value)| Self {
roots,
serialized,
value,
})
}
}
impl<E: EthSpec> Case for SszStaticWithSpec<BeaconBlock<E>> {
fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> {
let spec = &testing_spec::<E>(fork_name);
@@ -135,6 +147,16 @@ impl<E: EthSpec> Case for SszStaticWithSpec<BeaconBlock<E>> {
}
}
impl<E: EthSpec> LoadCase for SszStaticWithSpec<SignedBeaconBlock<E>> {
fn load_from_dir(path: &Path, fork_name: ForkName) -> Result<Self, Error> {
load_from_dir(path, fork_name).map(|(roots, serialized, value)| Self {
roots,
serialized,
value,
})
}
}
impl<E: EthSpec> Case for SszStaticWithSpec<SignedBeaconBlock<E>> {
fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> {
let spec = &testing_spec::<E>(fork_name);
@@ -145,3 +167,27 @@ impl<E: EthSpec> Case for SszStaticWithSpec<SignedBeaconBlock<E>> {
Ok(())
}
}
impl<E: EthSpec> LoadCase for SszStaticWithSpec<DataColumnsByRootIdentifierWrapper<E>> {
fn load_from_dir(path: &Path, fork_name: ForkName) -> Result<Self, Error> {
let spec = &testing_spec::<E>(fork_name);
let context = (fork_name, spec.number_of_columns as usize);
load_from_dir_with_context(path, context).map(|(roots, serialized, value)| Self {
roots,
serialized,
value,
})
}
}
impl<E: EthSpec> Case for SszStaticWithSpec<DataColumnsByRootIdentifierWrapper<E>> {
fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> {
let spec = &testing_spec::<E>(fork_name);
check_serialization(&self.value, &self.serialized, |bytes| {
DataColumnsByRootIdentifier::from_ssz_bytes(bytes, spec.number_of_columns as usize)
.map(Into::into)
})?;
check_tree_hash(&self.roots.root, self.value.tree_hash_root().as_slice())?;
Ok(())
}
}