use crate::cases::{self, Case, Cases, EpochTransition, LoadCase, Operation}; use crate::type_name; use crate::type_name::TypeName; use derivative::Derivative; use std::fs::{self, DirEntry}; use std::marker::PhantomData; use std::path::PathBuf; use types::{BeaconState, EthSpec, ForkName}; pub trait Handler { type Case: Case + LoadCase; fn config_name() -> &'static str { "general" } fn runner_name() -> &'static str; fn handler_name(&self) -> String; fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool { Self::Case::is_enabled_for_fork(fork_name) } fn run(&self) { for fork_name in ForkName::list_all() { if self.is_enabled_for_fork(fork_name) { self.run_for_fork(fork_name) } } } fn use_rayon() -> bool { true } fn run_for_fork(&self, fork_name: ForkName) { let fork_name_str = fork_name.to_string(); let handler_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("consensus-spec-tests") .join("tests") .join(Self::config_name()) .join(&fork_name_str) .join(Self::runner_name()) .join(self.handler_name()); // Iterate through test suites let as_directory = |entry: Result| -> Option { entry .ok() .filter(|e| e.file_type().map(|ty| ty.is_dir()).unwrap()) }; let test_cases = fs::read_dir(&handler_path) .unwrap_or_else(|e| panic!("handler dir {} exists: {:?}", handler_path.display(), e)) .filter_map(as_directory) .flat_map(|suite| fs::read_dir(suite.path()).expect("suite dir exists")) .filter_map(as_directory) .map(|test_case_dir| { let path = test_case_dir.path(); let case = Self::Case::load_from_dir(&path, fork_name).expect("test should load"); (path, case) }) .collect(); let results = Cases { test_cases }.test_results(fork_name, Self::use_rayon()); let name = format!( "{}/{}/{}", fork_name_str, Self::runner_name(), self.handler_name() ); crate::results::assert_tests_pass(&name, &handler_path, &results); } } macro_rules! bls_eth_handler { ($runner_name: ident, $case_name:ident, $handler_name:expr) => { #[derive(Derivative)] #[derivative(Default(bound = ""))] pub struct $runner_name; impl Handler for $runner_name { type Case = cases::$case_name; fn runner_name() -> &'static str { "bls" } fn handler_name(&self) -> String { $handler_name.into() } } }; } macro_rules! bls_handler { ($runner_name: ident, $case_name:ident, $handler_name:expr) => { #[derive(Derivative)] #[derivative(Default(bound = ""))] pub struct $runner_name; impl Handler for $runner_name { type Case = cases::$case_name; fn runner_name() -> &'static str { "bls" } fn config_name() -> &'static str { "bls12-381-tests" } fn handler_name(&self) -> String { $handler_name.into() } fn run(&self) { let fork_name = ForkName::Base; let fork_name_str = fork_name.to_string(); let handler_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("consensus-spec-tests") .join(Self::config_name()) .join(self.handler_name()); let as_file = |entry: Result| -> Option { entry .ok() .filter(|e| e.file_type().map(|ty| ty.is_file()).unwrap_or(false)) }; let test_cases: Vec<(PathBuf, Self::Case)> = fs::read_dir(&handler_path) .expect("handler dir exists") .filter_map(as_file) .map(|test_case_path| { let path = test_case_path.path(); let case = Self::Case::load_from_dir(&path, fork_name).expect("test should load"); (path, case) }) .collect(); let results = Cases { test_cases }.test_results(fork_name, Self::use_rayon()); let name = format!( "{}/{}/{}", fork_name_str, Self::runner_name(), self.handler_name() ); crate::results::assert_tests_pass(&name, &handler_path, &results); } } }; } bls_handler!(BlsAggregateSigsHandler, BlsAggregateSigs, "aggregate"); bls_handler!(BlsSignMsgHandler, BlsSign, "sign"); bls_handler!(BlsBatchVerifyHandler, BlsBatchVerify, "batch_verify"); bls_handler!(BlsVerifyMsgHandler, BlsVerify, "verify"); bls_handler!( BlsAggregateVerifyHandler, BlsAggregateVerify, "aggregate_verify" ); bls_handler!( BlsFastAggregateVerifyHandler, BlsFastAggregateVerify, "fast_aggregate_verify" ); bls_eth_handler!( BlsEthAggregatePubkeysHandler, BlsEthAggregatePubkeys, "eth_aggregate_pubkeys" ); bls_eth_handler!( BlsEthFastAggregateVerifyHandler, BlsEthFastAggregateVerify, "eth_fast_aggregate_verify" ); /// Handler for SSZ types. pub struct SszStaticHandler { supported_forks: Vec, _phantom: PhantomData<(T, E)>, } impl Default for SszStaticHandler { fn default() -> Self { Self::for_forks(ForkName::list_all()) } } impl SszStaticHandler { pub fn for_forks(supported_forks: Vec) -> Self { SszStaticHandler { supported_forks, _phantom: PhantomData, } } pub fn base_only() -> Self { Self::for_forks(vec![ForkName::Base]) } pub fn altair_only() -> Self { Self::for_forks(vec![ForkName::Altair]) } pub fn merge_only() -> Self { Self::for_forks(vec![ForkName::Merge]) } pub fn capella_only() -> Self { Self::for_forks(vec![ForkName::Capella]) } pub fn eip4844_only() -> Self { Self::for_forks(vec![ForkName::Eip4844]) } pub fn altair_and_later() -> Self { Self::for_forks(ForkName::list_all()[1..].to_vec()) } pub fn merge_and_later() -> Self { Self::for_forks(ForkName::list_all()[2..].to_vec()) } pub fn capella_and_later() -> Self { Self::for_forks(ForkName::list_all()[3..].to_vec()) } } /// Handler for SSZ types that implement `CachedTreeHash`. #[derive(Derivative)] #[derivative(Default(bound = ""))] pub struct SszStaticTHCHandler(PhantomData<(T, E)>); /// Handler for SSZ types that don't implement `ssz::Decode`. #[derive(Derivative)] #[derivative(Default(bound = ""))] pub struct SszStaticWithSpecHandler(PhantomData<(T, E)>); impl Handler for SszStaticHandler where T: cases::SszStaticType + ssz::Decode + TypeName, E: TypeName, { type Case = cases::SszStatic; fn config_name() -> &'static str { E::name() } fn runner_name() -> &'static str { "ssz_static" } fn handler_name(&self) -> String { T::name().into() } fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool { self.supported_forks.contains(&fork_name) } } impl Handler for SszStaticTHCHandler, E> where E: EthSpec + TypeName, { type Case = cases::SszStaticTHC>; fn config_name() -> &'static str { E::name() } fn runner_name() -> &'static str { "ssz_static" } fn handler_name(&self) -> String { BeaconState::::name().into() } } impl Handler for SszStaticWithSpecHandler where T: TypeName, E: EthSpec + TypeName, cases::SszStaticWithSpec: Case + LoadCase, { type Case = cases::SszStaticWithSpec; fn config_name() -> &'static str { E::name() } fn runner_name() -> &'static str { "ssz_static" } fn handler_name(&self) -> String { T::name().into() } } #[derive(Derivative)] #[derivative(Default(bound = ""))] pub struct ShufflingHandler(PhantomData); impl Handler for ShufflingHandler { type Case = cases::Shuffling; fn config_name() -> &'static str { E::name() } fn runner_name() -> &'static str { "shuffling" } fn handler_name(&self) -> String { "core".into() } fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool { fork_name == ForkName::Base } } #[derive(Derivative)] #[derivative(Default(bound = ""))] pub struct SanityBlocksHandler(PhantomData); impl Handler for SanityBlocksHandler { type Case = cases::SanityBlocks; fn config_name() -> &'static str { E::name() } fn runner_name() -> &'static str { "sanity" } fn handler_name(&self) -> String { "blocks".into() } fn is_enabled_for_fork(&self, _fork_name: ForkName) -> bool { // NOTE: v1.1.0-beta.4 doesn't mark the historical blocks test as requiring real crypto, so // only run these tests with real crypto for now. cfg!(not(feature = "fake_crypto")) } } #[derive(Derivative)] #[derivative(Default(bound = ""))] pub struct SanitySlotsHandler(PhantomData); impl Handler for SanitySlotsHandler { type Case = cases::SanitySlots; fn config_name() -> &'static str { E::name() } fn runner_name() -> &'static str { "sanity" } fn handler_name(&self) -> String { "slots".into() } } #[derive(Derivative)] #[derivative(Default(bound = ""))] pub struct RandomHandler(PhantomData); impl Handler for RandomHandler { type Case = cases::SanityBlocks; fn config_name() -> &'static str { E::name() } fn runner_name() -> &'static str { "random" } fn handler_name(&self) -> String { "random".into() } } #[derive(Derivative)] #[derivative(Default(bound = ""))] pub struct EpochProcessingHandler(PhantomData<(E, T)>); impl> Handler for EpochProcessingHandler { type Case = cases::EpochProcessing; fn config_name() -> &'static str { E::name() } fn runner_name() -> &'static str { "epoch_processing" } fn handler_name(&self) -> String { T::name().into() } } pub struct RewardsHandler { handler_name: &'static str, _phantom: PhantomData, } impl RewardsHandler { pub fn new(handler_name: &'static str) -> Self { Self { handler_name, _phantom: PhantomData, } } } impl Handler for RewardsHandler { type Case = cases::RewardsTest; fn config_name() -> &'static str { E::name() } fn runner_name() -> &'static str { "rewards" } fn handler_name(&self) -> String { self.handler_name.to_string() } } #[derive(Derivative)] #[derivative(Default(bound = ""))] pub struct ForkHandler(PhantomData); impl Handler for ForkHandler { type Case = cases::ForkTest; fn config_name() -> &'static str { E::name() } fn runner_name() -> &'static str { "fork" } fn handler_name(&self) -> String { "fork".into() } } #[derive(Derivative)] #[derivative(Default(bound = ""))] pub struct TransitionHandler(PhantomData); impl Handler for TransitionHandler { type Case = cases::TransitionTest; fn config_name() -> &'static str { E::name() } fn runner_name() -> &'static str { "transition" } fn handler_name(&self) -> String { "core".into() } } #[derive(Derivative)] #[derivative(Default(bound = ""))] pub struct FinalityHandler(PhantomData); impl Handler for FinalityHandler { // Reuse the blocks case runner. type Case = cases::SanityBlocks; fn config_name() -> &'static str { E::name() } fn runner_name() -> &'static str { "finality" } fn handler_name(&self) -> String { "finality".into() } } pub struct ForkChoiceHandler { handler_name: String, _phantom: PhantomData, } impl ForkChoiceHandler { pub fn new(handler_name: &str) -> Self { Self { handler_name: handler_name.into(), _phantom: PhantomData, } } } impl Handler for ForkChoiceHandler { type Case = cases::ForkChoiceTest; fn config_name() -> &'static str { E::name() } fn runner_name() -> &'static str { "fork_choice" } fn handler_name(&self) -> String { self.handler_name.clone() } fn use_rayon() -> bool { // The fork choice tests use `block_on` which can cause panics with rayon. false } fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool { // Merge block tests are only enabled for Bellatrix. if self.handler_name == "on_merge_block" && fork_name != ForkName::Merge { return false; } // These tests check block validity (which may include signatures) and there is no need to // run them with fake crypto. cfg!(not(feature = "fake_crypto")) } } #[derive(Derivative)] #[derivative(Default(bound = ""))] pub struct OptimisticSyncHandler(PhantomData); impl Handler for OptimisticSyncHandler { type Case = cases::ForkChoiceTest; fn config_name() -> &'static str { E::name() } fn runner_name() -> &'static str { "sync" } fn handler_name(&self) -> String { "optimistic".into() } fn use_rayon() -> bool { // The opt sync tests use `block_on` which can cause panics with rayon. false } fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool { fork_name != ForkName::Base && fork_name != ForkName::Altair && cfg!(not(feature = "fake_crypto")) } } #[derive(Derivative)] #[derivative(Default(bound = ""))] pub struct GenesisValidityHandler(PhantomData); impl Handler for GenesisValidityHandler { type Case = cases::GenesisValidity; fn config_name() -> &'static str { E::name() } fn runner_name() -> &'static str { "genesis" } fn handler_name(&self) -> String { "validity".into() } } #[derive(Derivative)] #[derivative(Default(bound = ""))] pub struct GenesisInitializationHandler(PhantomData); impl Handler for GenesisInitializationHandler { type Case = cases::GenesisInitialization; fn config_name() -> &'static str { E::name() } fn runner_name() -> &'static str { "genesis" } fn handler_name(&self) -> String { "initialization".into() } } #[derive(Derivative)] #[derivative(Default(bound = ""))] pub struct OperationsHandler(PhantomData<(E, O)>); impl> Handler for OperationsHandler { type Case = cases::Operations; fn config_name() -> &'static str { E::name() } fn runner_name() -> &'static str { "operations" } fn handler_name(&self) -> String { O::handler_name() } } #[derive(Derivative)] #[derivative(Default(bound = ""))] pub struct SszGenericHandler(PhantomData); impl Handler for SszGenericHandler { type Case = cases::SszGeneric; fn config_name() -> &'static str { "general" } fn runner_name() -> &'static str { "ssz_generic" } fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool { // SSZ generic tests are genesis only fork_name == ForkName::Base } fn handler_name(&self) -> String { H::name().into() } } // Supported SSZ generic handlers pub struct BasicVector; type_name!(BasicVector, "basic_vector"); pub struct Bitlist; type_name!(Bitlist, "bitlist"); pub struct Bitvector; type_name!(Bitvector, "bitvector"); pub struct Boolean; type_name!(Boolean, "boolean"); pub struct Uints; type_name!(Uints, "uints"); pub struct Containers; type_name!(Containers, "containers");