Directory Restructure (#1163)

* Move tests -> testing

* Directory restructure

* Update Cargo.toml during restructure

* Update Makefile during restructure

* Fix arbitrary path
This commit is contained in:
Paul Hauner
2020-05-18 21:24:23 +10:00
committed by GitHub
parent c571afb8d8
commit 4331834003
358 changed files with 217 additions and 229 deletions

View File

@@ -0,0 +1,29 @@
use self::BlsSetting::*;
use crate::error::Error;
use serde_repr::Deserialize_repr;
#[derive(Deserialize_repr, Debug, Clone, Copy)]
#[repr(u8)]
pub enum BlsSetting {
Flexible = 0,
Required = 1,
Ignored = 2,
}
impl Default for BlsSetting {
fn default() -> Self {
Flexible
}
}
impl BlsSetting {
/// Check the BLS setting and skip the test if it isn't compatible with the crypto config.
pub fn check(self) -> Result<(), Error> {
match self {
Flexible => Ok(()),
Required if !cfg!(feature = "fake_crypto") => Ok(()),
Ignored if cfg!(feature = "fake_crypto") => Ok(()),
_ => Err(Error::SkippedBls),
}
}
}

View File

@@ -0,0 +1,122 @@
use super::*;
use compare_fields::{CompareFields, Comparison, FieldComparison};
use std::fmt::Debug;
use std::path::{Path, PathBuf};
use types::BeaconState;
pub const MAX_VALUE_STRING_LEN: usize = 500;
#[derive(Debug, PartialEq, Clone)]
pub struct CaseResult {
pub case_index: usize,
pub desc: String,
pub path: PathBuf,
pub result: Result<(), Error>,
}
impl CaseResult {
pub fn new(
case_index: usize,
path: &Path,
case: &impl Case,
result: Result<(), Error>,
) -> Self {
CaseResult {
case_index,
desc: case.description(),
path: path.into(),
result,
}
}
}
/// Same as `compare_result_detailed`, however it drops the caches on both states before
/// comparison.
pub fn compare_beacon_state_results_without_caches<T: EthSpec, E: Debug>(
result: &mut Result<BeaconState<T>, E>,
expected: &mut Option<BeaconState<T>>,
) -> Result<(), Error> {
if let (Ok(ref mut result), Some(ref mut expected)) = (result.as_mut(), expected.as_mut()) {
result.drop_all_caches();
expected.drop_all_caches();
}
compare_result_detailed(&result, &expected)
}
/// Same as `compare_result`, however utilizes the `CompareFields` trait to give a list of
/// mismatching fields when `Ok(result) != Some(expected)`.
pub fn compare_result_detailed<T, E>(
result: &Result<T, E>,
expected: &Option<T>,
) -> Result<(), Error>
where
T: PartialEq<T> + Debug + CompareFields,
E: Debug,
{
match (result, expected) {
(Ok(result), Some(expected)) => {
let mut mismatching_fields: Vec<Comparison> = expected
.compare_fields(result)
.into_iter()
// Filter all out all fields that are equal.
.filter(Comparison::not_equal)
.collect();
mismatching_fields
.iter_mut()
.for_each(|f| f.retain_children(FieldComparison::not_equal));
if !mismatching_fields.is_empty() {
Err(Error::NotEqual(format!(
"Fields not equal (a = expected, b = result): {:#?}",
mismatching_fields
)))
} else {
Ok(())
}
}
_ => compare_result(result, expected),
}
}
/// Compares `result` with `expected`.
///
/// If `expected.is_none()` then `result` is expected to be `Err`. Otherwise, `T` in `result` and
/// `expected` must be equal.
pub fn compare_result<T, E>(result: &Result<T, E>, expected: &Option<T>) -> Result<(), Error>
where
T: PartialEq<T> + Debug,
E: Debug,
{
match (result, expected) {
// Pass: The should have failed and did fail.
(Err(_), None) => Ok(()),
// Fail: The test failed when it should have produced a result (fail).
(Err(e), Some(expected)) => Err(Error::NotEqual(format!(
"Got {:?} | Expected {:?}",
e,
fmt_val(expected)
))),
// Fail: The test produced a result when it should have failed (fail).
(Ok(result), None) => Err(Error::DidntFail(format!("Got {:?}", fmt_val(result)))),
// Potential Pass: The test should have produced a result, and it did.
(Ok(result), Some(expected)) => {
if result == expected {
Ok(())
} else {
Err(Error::NotEqual(format!(
"Got {:?} | Expected {:?}",
fmt_val(result),
fmt_val(expected)
)))
}
}
}
}
fn fmt_val<T: Debug>(val: T) -> String {
let mut string = format!("{:?}", val);
string.truncate(MAX_VALUE_STRING_LEN);
string
}

View File

@@ -0,0 +1,71 @@
use super::*;
use rayon::prelude::*;
use std::fmt::Debug;
use std::path::{Path, PathBuf};
mod bls_aggregate_sigs;
mod bls_aggregate_verify;
mod bls_fast_aggregate_verify;
mod bls_sign_msg;
mod bls_verify_msg;
mod common;
mod epoch_processing;
mod genesis_initialization;
mod genesis_validity;
mod operations;
mod sanity_blocks;
mod sanity_slots;
mod shuffling;
mod ssz_generic;
mod ssz_static;
pub use bls_aggregate_sigs::*;
pub use bls_aggregate_verify::*;
pub use bls_fast_aggregate_verify::*;
pub use bls_sign_msg::*;
pub use bls_verify_msg::*;
pub use common::SszStaticType;
pub use epoch_processing::*;
pub use genesis_initialization::*;
pub use genesis_validity::*;
pub use operations::*;
pub use sanity_blocks::*;
pub use sanity_slots::*;
pub use shuffling::*;
pub use ssz_generic::*;
pub use ssz_static::*;
pub trait LoadCase: Sized {
/// Load the test case from a test case directory.
fn load_from_dir(_path: &Path) -> Result<Self, Error>;
}
pub trait Case: Debug + Sync {
/// An optional field for implementing a custom description.
///
/// Defaults to "no description".
fn description(&self) -> String {
"no description".to_string()
}
/// Execute a test and return the result.
///
/// `case_index` reports the index of the case in the set of test cases. It is not strictly
/// necessary, but it's useful when troubleshooting specific failing tests.
fn result(&self, case_index: usize) -> Result<(), Error>;
}
#[derive(Debug)]
pub struct Cases<T> {
pub test_cases: Vec<(PathBuf, T)>,
}
impl<T: Case> Cases<T> {
pub fn test_results(&self) -> Vec<CaseResult> {
self.test_cases
.into_par_iter()
.enumerate()
.map(|(i, (ref path, ref tc))| CaseResult::new(i, path, tc, tc.result(i)))
.collect()
}
}

View File

@@ -0,0 +1,36 @@
use super::*;
use crate::case_result::compare_result;
use crate::cases::common::BlsCase;
use bls::{AggregateSignature, Signature};
use serde_derive::Deserialize;
#[derive(Debug, Clone, Deserialize)]
pub struct BlsAggregateSigs {
pub input: Vec<String>,
pub output: String,
}
impl BlsCase for BlsAggregateSigs {}
impl Case for BlsAggregateSigs {
fn result(&self, _case_index: usize) -> Result<(), Error> {
let mut aggregate_signature = AggregateSignature::new();
for key_str in &self.input {
let sig = hex::decode(&key_str[2..])
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
let sig = Signature::from_bytes(&sig)
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
aggregate_signature.add(&sig);
}
let output_bytes = Some(
hex::decode(&self.output[2..])
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?,
);
let aggregate_signature = Ok(aggregate_signature.as_bytes());
compare_result::<Vec<u8>, Vec<u8>>(&aggregate_signature, &output_bytes)
}
}

View File

@@ -0,0 +1,59 @@
use super::*;
use crate::case_result::compare_result;
use crate::cases::common::BlsCase;
use bls::{AggregateSignature, PublicKey};
use serde_derive::Deserialize;
#[derive(Debug, Clone, Deserialize)]
pub struct BlsAggregatePair {
pub pubkey: PublicKey,
pub message: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BlsAggregateVerifyInput {
pub pairs: Vec<BlsAggregatePair>,
pub signature: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BlsAggregateVerify {
pub input: BlsAggregateVerifyInput,
pub output: bool,
}
impl BlsCase for BlsAggregateVerify {}
impl Case for BlsAggregateVerify {
fn result(&self, _case_index: usize) -> Result<(), Error> {
let messages = self
.input
.pairs
.iter()
.map(|pair| {
hex::decode(&pair.message[2..])
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))
})
.collect::<Result<Vec<Vec<_>>, _>>()?;
let message_refs = messages
.iter()
.map(|x| x.as_slice())
.collect::<Vec<&[u8]>>();
let pubkey_refs = self
.input
.pairs
.iter()
.map(|p| &p.pubkey)
.collect::<Vec<_>>();
let signature_ok = hex::decode(&self.input.signature[2..])
.ok()
.and_then(|bytes: Vec<u8>| AggregateSignature::from_bytes(&bytes).ok())
.map(|signature| signature.verify_multiple(&message_refs, &pubkey_refs))
.unwrap_or(false);
compare_result::<bool, ()>(&Ok(signature_ok), &Some(self.output))
}
}

View File

@@ -0,0 +1,50 @@
use super::*;
use crate::case_result::compare_result;
use crate::cases::common::BlsCase;
use bls::{AggregatePublicKey, AggregateSignature, PublicKey, PublicKeyBytes};
use serde_derive::Deserialize;
use std::convert::TryInto;
#[derive(Debug, Clone, Deserialize)]
pub struct BlsFastAggregateVerifyInput {
pub pubkeys: Vec<PublicKeyBytes>,
pub message: String,
pub signature: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BlsFastAggregateVerify {
pub input: BlsFastAggregateVerifyInput,
pub output: bool,
}
impl BlsCase for BlsFastAggregateVerify {}
impl Case for BlsFastAggregateVerify {
fn result(&self, _case_index: usize) -> Result<(), Error> {
let message = hex::decode(&self.input.message[2..])
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
let signature_ok = self
.input
.pubkeys
.iter()
.try_fold(
AggregatePublicKey::new(),
|mut agg, pkb| -> Option<AggregatePublicKey> {
let pk: Result<PublicKey, ssz::DecodeError> = pkb.try_into();
agg.add(&pk.ok()?);
Some(agg)
},
)
.and_then(|aggregate_pubkey| {
hex::decode(&self.input.signature[2..])
.ok()
.and_then(|bytes: Vec<u8>| AggregateSignature::from_bytes(&bytes).ok())
.map(|signature| signature.verify(&message, &aggregate_pubkey))
})
.unwrap_or(false);
compare_result::<bool, ()>(&Ok(signature_ok), &Some(self.output))
}
}

View File

@@ -0,0 +1,38 @@
use super::*;
use crate::case_result::compare_result;
use crate::cases::common::BlsCase;
use bls::{SecretKey, Signature};
use serde_derive::Deserialize;
#[derive(Debug, Clone, Deserialize)]
pub struct BlsSignInput {
pub privkey: String,
pub message: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BlsSign {
pub input: BlsSignInput,
pub output: String,
}
impl BlsCase for BlsSign {}
impl Case for BlsSign {
fn result(&self, _case_index: usize) -> Result<(), Error> {
// Convert private_key and message to required types
let sk = hex::decode(&self.input.privkey[2..])
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
let sk = SecretKey::from_bytes(&sk).unwrap();
let msg = hex::decode(&self.input.message[2..])
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
let signature = Signature::new(&msg, &sk);
// Convert the output to one set of bytes
let decoded = hex::decode(&self.output[2..])
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
compare_result::<Vec<u8>, Vec<u8>>(&Ok(signature.as_bytes()), &Some(decoded))
}
}

View File

@@ -0,0 +1,35 @@
use super::*;
use crate::case_result::compare_result;
use crate::cases::common::BlsCase;
use bls::{PublicKey, Signature, SignatureBytes};
use serde_derive::Deserialize;
use std::convert::TryInto;
#[derive(Debug, Clone, Deserialize)]
pub struct BlsVerifyInput {
pub pubkey: PublicKey,
pub message: String,
pub signature: SignatureBytes,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BlsVerify {
pub input: BlsVerifyInput,
pub output: bool,
}
impl BlsCase for BlsVerify {}
impl Case for BlsVerify {
fn result(&self, _case_index: usize) -> Result<(), Error> {
let message = hex::decode(&self.input.message[2..])
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
let signature_ok = (&self.input.signature)
.try_into()
.map(|signature: Signature| signature.verify(&message, &self.input.pubkey))
.unwrap_or(false);
compare_result::<bool, ()>(&Ok(signature_ok), &Some(self.output))
}
}

View File

@@ -0,0 +1,72 @@
use crate::cases::LoadCase;
use crate::decode::yaml_decode_file;
use crate::error::Error;
use serde_derive::Deserialize;
use ssz::{Decode, Encode};
use ssz_derive::{Decode, Encode};
use std::convert::TryFrom;
use std::fmt::Debug;
use std::path::Path;
use tree_hash::TreeHash;
/// Trait for all BLS cases to eliminate some boilerplate.
pub trait BlsCase: serde::de::DeserializeOwned {}
impl<T: BlsCase> LoadCase for T {
fn load_from_dir(path: &Path) -> Result<Self, Error> {
yaml_decode_file(&path.join("data.yaml"))
}
}
/// Macro to wrap U128 and U256 so they deserialize correctly.
macro_rules! uint_wrapper {
($wrapper_name:ident, $wrapped_type:ty) => {
#[derive(Debug, Clone, Copy, Default, PartialEq, Decode, Encode, Deserialize)]
#[serde(try_from = "String")]
pub struct $wrapper_name {
pub x: $wrapped_type,
}
impl TryFrom<String> for $wrapper_name {
type Error = String;
fn try_from(s: String) -> Result<Self, Self::Error> {
<$wrapped_type>::from_dec_str(&s)
.map(|x| Self { x })
.map_err(|e| format!("{:?}", e))
}
}
impl tree_hash::TreeHash for $wrapper_name {
fn tree_hash_type() -> tree_hash::TreeHashType {
<$wrapped_type>::tree_hash_type()
}
fn tree_hash_packed_encoding(&self) -> Vec<u8> {
self.x.tree_hash_packed_encoding()
}
fn tree_hash_packing_factor() -> usize {
<$wrapped_type>::tree_hash_packing_factor()
}
fn tree_hash_root(&self) -> tree_hash::Hash256 {
self.x.tree_hash_root()
}
}
};
}
uint_wrapper!(TestU128, ethereum_types::U128);
uint_wrapper!(TestU256, ethereum_types::U256);
/// Trait alias for all deez bounds
pub trait SszStaticType:
serde::de::DeserializeOwned + Decode + Encode + TreeHash + Clone + PartialEq + Debug + Sync
{
}
impl<T> SszStaticType for T where
T: serde::de::DeserializeOwned + Decode + Encode + TreeHash + Clone + PartialEq + Debug + Sync
{
}

View File

@@ -0,0 +1,148 @@
use super::*;
use crate::bls_setting::BlsSetting;
use crate::case_result::compare_beacon_state_results_without_caches;
use crate::decode::{ssz_decode_file, yaml_decode_file};
use crate::type_name;
use crate::type_name::TypeName;
use serde_derive::Deserialize;
use state_processing::per_epoch_processing::{
errors::EpochProcessingError, process_final_updates, process_justification_and_finalization,
process_registry_updates, process_rewards_and_penalties, process_slashings,
validator_statuses::ValidatorStatuses,
};
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use types::{BeaconState, ChainSpec, EthSpec};
#[derive(Debug, Clone, Default, Deserialize)]
pub struct Metadata {
pub description: Option<String>,
pub bls_setting: Option<BlsSetting>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(bound = "E: EthSpec")]
pub struct EpochProcessing<E: EthSpec, T: EpochTransition<E>> {
pub path: PathBuf,
pub metadata: Metadata,
pub pre: BeaconState<E>,
pub post: Option<BeaconState<E>>,
#[serde(skip_deserializing)]
_phantom: PhantomData<T>,
}
pub trait EpochTransition<E: EthSpec>: TypeName + Debug + Sync {
fn run(state: &mut BeaconState<E>, spec: &ChainSpec) -> Result<(), EpochProcessingError>;
}
#[derive(Debug)]
pub struct JustificationAndFinalization;
#[derive(Debug)]
pub struct RewardsAndPenalties;
#[derive(Debug)]
pub struct RegistryUpdates;
#[derive(Debug)]
pub struct Slashings;
#[derive(Debug)]
pub struct FinalUpdates;
type_name!(
JustificationAndFinalization,
"justification_and_finalization"
);
type_name!(RewardsAndPenalties, "rewards_and_penalties");
type_name!(RegistryUpdates, "registry_updates");
type_name!(Slashings, "slashings");
type_name!(FinalUpdates, "final_updates");
impl<E: EthSpec> EpochTransition<E> for JustificationAndFinalization {
fn run(state: &mut BeaconState<E>, spec: &ChainSpec) -> Result<(), EpochProcessingError> {
let mut validator_statuses = ValidatorStatuses::new(state, spec)?;
validator_statuses.process_attestations(state, spec)?;
process_justification_and_finalization(state, &validator_statuses.total_balances)
}
}
impl<E: EthSpec> EpochTransition<E> for RewardsAndPenalties {
fn run(state: &mut BeaconState<E>, spec: &ChainSpec) -> Result<(), EpochProcessingError> {
let mut validator_statuses = ValidatorStatuses::new(state, spec)?;
validator_statuses.process_attestations(state, spec)?;
process_rewards_and_penalties(state, &mut validator_statuses, spec)
}
}
impl<E: EthSpec> EpochTransition<E> for RegistryUpdates {
fn run(state: &mut BeaconState<E>, spec: &ChainSpec) -> Result<(), EpochProcessingError> {
process_registry_updates(state, spec)
}
}
impl<E: EthSpec> EpochTransition<E> for Slashings {
fn run(state: &mut BeaconState<E>, spec: &ChainSpec) -> Result<(), EpochProcessingError> {
let mut validator_statuses = ValidatorStatuses::new(&state, spec)?;
validator_statuses.process_attestations(&state, spec)?;
process_slashings(
state,
validator_statuses.total_balances.current_epoch(),
spec,
)?;
Ok(())
}
}
impl<E: EthSpec> EpochTransition<E> for FinalUpdates {
fn run(state: &mut BeaconState<E>, spec: &ChainSpec) -> Result<(), EpochProcessingError> {
process_final_updates(state, spec)
}
}
impl<E: EthSpec, T: EpochTransition<E>> LoadCase for EpochProcessing<E, T> {
fn load_from_dir(path: &Path) -> Result<Self, Error> {
let metadata_path = path.join("meta.yaml");
let metadata: Metadata = if metadata_path.is_file() {
yaml_decode_file(&metadata_path)?
} else {
Metadata::default()
};
let pre = ssz_decode_file(&path.join("pre.ssz"))?;
let post_file = path.join("post.ssz");
let post = if post_file.is_file() {
Some(ssz_decode_file(&post_file)?)
} else {
None
};
Ok(Self {
path: path.into(),
metadata,
pre,
post,
_phantom: PhantomData,
})
}
}
impl<E: EthSpec, T: EpochTransition<E>> Case for EpochProcessing<E, T> {
fn description(&self) -> String {
self.metadata
.description
.clone()
.unwrap_or_else(String::new)
}
fn result(&self, _case_index: usize) -> Result<(), Error> {
let mut state = self.pre.clone();
let mut expected = self.post.clone();
let spec = &E::default_spec();
let mut result = (|| {
// Processing requires the committee caches.
state.build_all_committee_caches(spec)?;
T::run(&mut state, spec).map(|_| state)
})();
compare_beacon_state_results_without_caches(&mut result, &mut expected)
}
}

View File

@@ -0,0 +1,62 @@
use super::*;
use crate::case_result::compare_beacon_state_results_without_caches;
use crate::decode::{ssz_decode_file, yaml_decode_file};
use serde_derive::Deserialize;
use state_processing::initialize_beacon_state_from_eth1;
use std::path::PathBuf;
use types::{BeaconState, Deposit, EthSpec, Hash256};
#[derive(Debug, Clone, Deserialize)]
struct Metadata {
deposits_count: usize,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(bound = "E: EthSpec")]
pub struct GenesisInitialization<E: EthSpec> {
pub path: PathBuf,
pub eth1_block_hash: Hash256,
pub eth1_timestamp: u64,
pub deposits: Vec<Deposit>,
pub state: Option<BeaconState<E>>,
}
impl<E: EthSpec> LoadCase for GenesisInitialization<E> {
fn load_from_dir(path: &Path) -> Result<Self, Error> {
let eth1_block_hash = ssz_decode_file(&path.join("eth1_block_hash.ssz"))?;
let eth1_timestamp = yaml_decode_file(&path.join("eth1_timestamp.yaml"))?;
let meta: Metadata = yaml_decode_file(&path.join("meta.yaml"))?;
let deposits: Vec<Deposit> = (0..meta.deposits_count)
.map(|i| {
let filename = format!("deposits_{}.ssz", i);
ssz_decode_file(&path.join(filename))
})
.collect::<Result<_, _>>()?;
let state = ssz_decode_file(&path.join("state.ssz"))?;
Ok(Self {
path: path.into(),
eth1_block_hash,
eth1_timestamp,
deposits,
state: Some(state),
})
}
}
impl<E: EthSpec> Case for GenesisInitialization<E> {
fn result(&self, _case_index: usize) -> Result<(), Error> {
let spec = &E::default_spec();
let mut result = initialize_beacon_state_from_eth1(
self.eth1_block_hash,
self.eth1_timestamp,
self.deposits.clone(),
spec,
);
let mut expected = self.state.clone();
compare_beacon_state_results_without_caches(&mut result, &mut expected)
}
}

View File

@@ -0,0 +1,39 @@
use super::*;
use crate::decode::{ssz_decode_file, yaml_decode_file};
use serde_derive::Deserialize;
use state_processing::is_valid_genesis_state;
use std::path::Path;
use types::{BeaconState, EthSpec};
#[derive(Debug, Clone, Deserialize)]
#[serde(bound = "E: EthSpec")]
pub struct GenesisValidity<E: EthSpec> {
pub genesis: BeaconState<E>,
pub is_valid: bool,
}
impl<E: EthSpec> LoadCase for GenesisValidity<E> {
fn load_from_dir(path: &Path) -> Result<Self, Error> {
let genesis = ssz_decode_file(&path.join("genesis.ssz"))?;
let is_valid = yaml_decode_file(&path.join("is_valid.yaml"))?;
Ok(Self { genesis, is_valid })
}
}
impl<E: EthSpec> Case for GenesisValidity<E> {
fn result(&self, _case_index: usize) -> Result<(), Error> {
let spec = &E::default_spec();
let is_valid = is_valid_genesis_state(&self.genesis, spec);
if is_valid == self.is_valid {
Ok(())
} else {
Err(Error::NotEqual(format!(
"Got {}, expected {}",
is_valid, self.is_valid
)))
}
}
}

View File

@@ -0,0 +1,180 @@
use super::*;
use crate::bls_setting::BlsSetting;
use crate::case_result::compare_beacon_state_results_without_caches;
use crate::decode::{ssz_decode_file, yaml_decode_file};
use crate::type_name::TypeName;
use serde_derive::Deserialize;
use ssz::Decode;
use state_processing::per_block_processing::{
errors::BlockProcessingError, process_attestations, process_attester_slashings,
process_block_header, process_deposits, process_exits, process_proposer_slashings,
VerifySignatures,
};
use std::fmt::Debug;
use std::path::Path;
use types::{
Attestation, AttesterSlashing, BeaconBlock, BeaconState, ChainSpec, Deposit, EthSpec,
ProposerSlashing, SignedVoluntaryExit,
};
#[derive(Debug, Clone, Default, Deserialize)]
struct Metadata {
description: Option<String>,
bls_setting: Option<BlsSetting>,
}
#[derive(Debug, Clone)]
pub struct Operations<E: EthSpec, O: Operation<E>> {
metadata: Metadata,
pub pre: BeaconState<E>,
pub operation: O,
pub post: Option<BeaconState<E>>,
}
pub trait Operation<E: EthSpec>: Decode + TypeName + Debug + Sync {
fn handler_name() -> String {
Self::name().to_lowercase()
}
fn filename() -> String {
format!("{}.ssz", Self::handler_name())
}
fn apply_to(
&self,
state: &mut BeaconState<E>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError>;
}
impl<E: EthSpec> Operation<E> for Attestation<E> {
fn apply_to(
&self,
state: &mut BeaconState<E>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
process_attestations(state, &[self.clone()], VerifySignatures::True, spec)
}
}
impl<E: EthSpec> Operation<E> for AttesterSlashing<E> {
fn handler_name() -> String {
"attester_slashing".into()
}
fn apply_to(
&self,
state: &mut BeaconState<E>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
process_attester_slashings(state, &[self.clone()], VerifySignatures::True, spec)
}
}
impl<E: EthSpec> Operation<E> for Deposit {
fn apply_to(
&self,
state: &mut BeaconState<E>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
process_deposits(state, &[self.clone()], spec)
}
}
impl<E: EthSpec> Operation<E> for ProposerSlashing {
fn handler_name() -> String {
"proposer_slashing".into()
}
fn apply_to(
&self,
state: &mut BeaconState<E>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
process_proposer_slashings(state, &[self.clone()], VerifySignatures::True, spec)
}
}
impl<E: EthSpec> Operation<E> for SignedVoluntaryExit {
fn handler_name() -> String {
"voluntary_exit".into()
}
fn apply_to(
&self,
state: &mut BeaconState<E>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
process_exits(state, &[self.clone()], VerifySignatures::True, spec)
}
}
impl<E: EthSpec> Operation<E> for BeaconBlock<E> {
fn handler_name() -> String {
"block_header".into()
}
fn filename() -> String {
"block.ssz".into()
}
fn apply_to(
&self,
state: &mut BeaconState<E>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
Ok(process_block_header(state, self, spec)?)
}
}
impl<E: EthSpec, O: Operation<E>> LoadCase for Operations<E, O> {
fn load_from_dir(path: &Path) -> Result<Self, Error> {
let metadata_path = path.join("meta.yaml");
let metadata: Metadata = if metadata_path.is_file() {
yaml_decode_file(&metadata_path)?
} else {
Metadata::default()
};
let pre = ssz_decode_file(&path.join("pre.ssz"))?;
let operation = ssz_decode_file(&path.join(O::filename()))?;
let post_filename = path.join("post.ssz");
let post = if post_filename.is_file() {
Some(ssz_decode_file(&post_filename)?)
} else {
None
};
Ok(Self {
metadata,
pre,
operation,
post,
})
}
}
impl<E: EthSpec, O: Operation<E>> Case for Operations<E, O> {
fn description(&self) -> String {
self.metadata
.description
.clone()
.unwrap_or_else(String::new)
}
fn result(&self, _case_index: usize) -> Result<(), Error> {
self.metadata.bls_setting.unwrap_or_default().check()?;
let spec = &E::default_spec();
let mut state = self.pre.clone();
let mut expected = self.post.clone();
// Processing requires the committee caches.
state
.build_all_committee_caches(spec)
.expect("committee caches OK");
let mut result = self.operation.apply_to(&mut state, spec).map(|()| state);
compare_beacon_state_results_without_caches(&mut result, &mut expected)
}
}

View File

@@ -0,0 +1,126 @@
use super::*;
use crate::bls_setting::BlsSetting;
use crate::case_result::compare_beacon_state_results_without_caches;
use crate::decode::{ssz_decode_file, yaml_decode_file};
use serde_derive::Deserialize;
use state_processing::{
per_block_processing, per_slot_processing, BlockProcessingError, BlockSignatureStrategy,
};
use types::{BeaconState, EthSpec, RelativeEpoch, SignedBeaconBlock};
#[derive(Debug, Clone, Deserialize)]
pub struct Metadata {
pub description: Option<String>,
pub bls_setting: Option<BlsSetting>,
pub blocks_count: usize,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(bound = "E: EthSpec")]
pub struct SanityBlocks<E: EthSpec> {
pub metadata: Metadata,
pub pre: BeaconState<E>,
pub blocks: Vec<SignedBeaconBlock<E>>,
pub post: Option<BeaconState<E>>,
}
impl<E: EthSpec> LoadCase for SanityBlocks<E> {
fn load_from_dir(path: &Path) -> Result<Self, Error> {
let metadata: Metadata = yaml_decode_file(&path.join("meta.yaml"))?;
let pre = ssz_decode_file(&path.join("pre.ssz"))?;
let blocks: Vec<SignedBeaconBlock<E>> = (0..metadata.blocks_count)
.map(|i| {
let filename = format!("blocks_{}.ssz", i);
ssz_decode_file(&path.join(filename))
})
.collect::<Result<_, _>>()?;
let post_file = path.join("post.ssz");
let post = if post_file.is_file() {
Some(ssz_decode_file(&post_file)?)
} else {
None
};
Ok(Self {
metadata,
pre,
blocks,
post,
})
}
}
impl<E: EthSpec> Case for SanityBlocks<E> {
fn description(&self) -> String {
self.metadata
.description
.clone()
.unwrap_or_else(String::new)
}
fn result(&self, _case_index: usize) -> Result<(), Error> {
self.metadata.bls_setting.unwrap_or_default().check()?;
let mut bulk_state = self.pre.clone();
let mut expected = self.post.clone();
let spec = &E::default_spec();
// Processing requires the epoch cache.
bulk_state.build_all_caches(spec).unwrap();
// Spawning a second state to call the VerifyIndiviual strategy to avoid bitrot.
// See https://github.com/sigp/lighthouse/issues/742.
let mut indiv_state = bulk_state.clone();
let result = self
.blocks
.iter()
.try_for_each(|signed_block| {
let block = &signed_block.message;
while bulk_state.slot < block.slot {
per_slot_processing(&mut bulk_state, None, spec).unwrap();
per_slot_processing(&mut indiv_state, None, spec).unwrap();
}
bulk_state
.build_committee_cache(RelativeEpoch::Current, spec)
.unwrap();
indiv_state
.build_committee_cache(RelativeEpoch::Current, spec)
.unwrap();
per_block_processing(
&mut indiv_state,
signed_block,
None,
BlockSignatureStrategy::VerifyIndividual,
spec,
)?;
per_block_processing(
&mut bulk_state,
signed_block,
None,
BlockSignatureStrategy::VerifyBulk,
spec,
)?;
if block.state_root == bulk_state.canonical_root()
&& block.state_root == indiv_state.canonical_root()
{
Ok(())
} else {
Err(BlockProcessingError::StateRootMismatch)
}
})
.map(|_| (bulk_state, indiv_state));
let (mut bulk_result, mut indiv_result) = match result {
Err(e) => (Err(e.clone()), Err(e)),
Ok(res) => (Ok(res.0), Ok(res.1)),
};
compare_beacon_state_results_without_caches(&mut indiv_result, &mut expected)?;
compare_beacon_state_results_without_caches(&mut bulk_result, &mut expected)
}
}

View File

@@ -0,0 +1,74 @@
use super::*;
use crate::bls_setting::BlsSetting;
use crate::case_result::compare_beacon_state_results_without_caches;
use crate::decode::{ssz_decode_file, yaml_decode_file};
use serde_derive::Deserialize;
use state_processing::per_slot_processing;
use types::{BeaconState, EthSpec};
#[derive(Debug, Clone, Default, Deserialize)]
pub struct Metadata {
pub description: Option<String>,
pub bls_setting: Option<BlsSetting>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(bound = "E: EthSpec")]
pub struct SanitySlots<E: EthSpec> {
pub metadata: Metadata,
pub pre: BeaconState<E>,
pub slots: u64,
pub post: Option<BeaconState<E>>,
}
impl<E: EthSpec> LoadCase for SanitySlots<E> {
fn load_from_dir(path: &Path) -> Result<Self, Error> {
let metadata_path = path.join("meta.yaml");
let metadata: Metadata = if metadata_path.is_file() {
yaml_decode_file(&metadata_path)?
} else {
Metadata::default()
};
let pre = ssz_decode_file(&path.join("pre.ssz"))?;
let slots: u64 = yaml_decode_file(&path.join("slots.yaml"))?;
let post_file = path.join("post.ssz");
let post = if post_file.is_file() {
Some(ssz_decode_file(&post_file)?)
} else {
None
};
Ok(Self {
metadata,
pre,
slots,
post,
})
}
}
impl<E: EthSpec> Case for SanitySlots<E> {
fn description(&self) -> String {
self.metadata
.description
.clone()
.unwrap_or_else(String::new)
}
fn result(&self, _case_index: usize) -> Result<(), Error> {
self.metadata.bls_setting.unwrap_or_default().check()?;
let mut state = self.pre.clone();
let mut expected = self.post.clone();
let spec = &E::default_spec();
// Processing requires the epoch cache.
state.build_all_caches(spec).unwrap();
let mut result = (0..self.slots)
.try_for_each(|_| per_slot_processing(&mut state, None, spec).map(|_| ()))
.map(|_| state);
compare_beacon_state_results_without_caches(&mut result, &mut expected)
}
}

View File

@@ -0,0 +1,48 @@
use super::*;
use crate::case_result::compare_result;
use crate::decode::yaml_decode_file;
use serde_derive::Deserialize;
use std::marker::PhantomData;
use swap_or_not_shuffle::{compute_shuffled_index, shuffle_list};
#[derive(Debug, Clone, Deserialize)]
pub struct Shuffling<T> {
pub seed: String,
pub count: usize,
pub mapping: Vec<usize>,
#[serde(skip)]
_phantom: PhantomData<T>,
}
impl<T: EthSpec> LoadCase for Shuffling<T> {
fn load_from_dir(path: &Path) -> Result<Self, Error> {
yaml_decode_file(&path.join("mapping.yaml"))
}
}
impl<T: EthSpec> Case for Shuffling<T> {
fn result(&self, _case_index: usize) -> Result<(), Error> {
if self.count == 0 {
compare_result::<_, Error>(&Ok(vec![]), &Some(self.mapping.clone()))?;
} else {
let spec = T::default_spec();
let seed = hex::decode(&self.seed[2..])
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
// Test compute_shuffled_index
let shuffling = (0..self.count)
.map(|i| {
compute_shuffled_index(i, self.count, &seed, spec.shuffle_round_count).unwrap()
})
.collect();
compare_result::<_, Error>(&Ok(shuffling), &Some(self.mapping.clone()))?;
// Test "shuffle_list"
let input: Vec<usize> = (0..self.count).collect();
let shuffling = shuffle_list(input, spec.shuffle_round_count, &seed, false).unwrap();
compare_result::<_, Error>(&Ok(shuffling), &Some(self.mapping.clone()))?;
}
Ok(())
}
}

View File

@@ -0,0 +1,299 @@
#![allow(non_snake_case)]
use super::*;
use crate::cases::common::{SszStaticType, TestU128, TestU256};
use crate::cases::ssz_static::{check_serialization, check_tree_hash};
use crate::decode::yaml_decode_file;
use serde::{de::Error as SerdeError, Deserializer};
use serde_derive::Deserialize;
use ssz_derive::{Decode, Encode};
use std::fs;
use std::path::{Path, PathBuf};
use tree_hash_derive::TreeHash;
use types::typenum::*;
use types::{BitList, BitVector, FixedVector, VariableList};
#[derive(Debug, Clone, Deserialize)]
struct Metadata {
root: String,
signing_root: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SszGeneric {
path: PathBuf,
handler_name: String,
case_name: String,
}
impl LoadCase for SszGeneric {
fn load_from_dir(path: &Path) -> Result<Self, Error> {
let components = path
.components()
.map(|c| c.as_os_str().to_string_lossy().into_owned())
.rev()
.collect::<Vec<_>>();
// Test case name is last
let case_name = components[0].clone();
// Handler name is third last, before suite name and case name
let handler_name = components[2].clone();
Ok(Self {
path: path.into(),
handler_name,
case_name,
})
}
}
macro_rules! type_dispatch {
($function:ident,
($($arg:expr),*),
$base_ty:tt,
<$($param_ty:ty),*>,
[ $value:expr => primitive_type ] $($rest:tt)*) => {
match $value {
"bool" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* bool>, $($rest)*),
"uint8" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* u8>, $($rest)*),
"uint16" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* u16>, $($rest)*),
"uint32" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* u32>, $($rest)*),
"uint64" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* u64>, $($rest)*),
"uint128" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* TestU128>, $($rest)*),
"uint256" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* TestU256>, $($rest)*),
_ => Err(Error::FailedToParseTest(format!("unsupported: {}", $value))),
}
};
($function:ident,
($($arg:expr),*),
$base_ty:tt,
<$($param_ty:ty),*>,
[ $value:expr => typenum ] $($rest:tt)*) => {
match $value {
// DO YOU LIKE NUMBERS?
"0" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U0>, $($rest)*),
"1" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U1>, $($rest)*),
"2" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U2>, $($rest)*),
"3" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U3>, $($rest)*),
"4" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U4>, $($rest)*),
"5" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U5>, $($rest)*),
"6" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U6>, $($rest)*),
"7" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U7>, $($rest)*),
"8" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U8>, $($rest)*),
"9" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U9>, $($rest)*),
"16" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U16>, $($rest)*),
"31" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U31>, $($rest)*),
"32" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U32>, $($rest)*),
"64" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U64>, $($rest)*),
"128" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U128>, $($rest)*),
"256" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U256>, $($rest)*),
"512" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U512>, $($rest)*),
"513" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U513>, $($rest)*),
"1024" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U1024>, $($rest)*),
"2048" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U2048>, $($rest)*),
"4096" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U4096>, $($rest)*),
"8192" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* U8192>, $($rest)*),
_ => Err(Error::FailedToParseTest(format!("unsupported: {}", $value))),
}
};
($function:ident,
($($arg:expr),*),
$base_ty:tt,
<$($param_ty:ty),*>,
[ $value:expr => test_container ] $($rest:tt)*) => {
match $value {
"SingleFieldTestStruct" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* SingleFieldTestStruct>, $($rest)*),
"SmallTestStruct" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* SmallTestStruct>, $($rest)*),
"FixedTestStruct" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* FixedTestStruct>, $($rest)*),
"VarTestStruct" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* VarTestStruct>, $($rest)*),
"ComplexTestStruct" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* ComplexTestStruct>, $($rest)*),
"BitsStruct" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* BitsStruct>, $($rest)*),
_ => Err(Error::FailedToParseTest(format!("unsupported: {}", $value))),
}
};
// No base type: apply type params to function
($function:ident, ($($arg:expr),*), _, <$($param_ty:ty),*>,) => {
$function::<$($param_ty),*>($($arg),*)
};
($function:ident, ($($arg:expr),*), $base_type_name:ident, <$($param_ty:ty),*>,) => {
$function::<$base_type_name<$($param_ty),*>>($($arg),*)
}
}
impl Case for SszGeneric {
fn result(&self, _case_index: usize) -> Result<(), Error> {
let parts = self.case_name.split('_').collect::<Vec<_>>();
match self.handler_name.as_str() {
"basic_vector" => {
let elem_ty = parts[1];
let length = parts[2];
type_dispatch!(
ssz_generic_test,
(&self.path),
FixedVector,
<>,
[elem_ty => primitive_type]
[length => typenum]
)?;
}
"bitlist" => {
let mut limit = parts[1];
// Test format is inconsistent, pretend the limit is 32 (arbitrary)
// https://github.com/ethereum/eth2.0-spec-tests
if limit == "no" {
limit = "32";
}
type_dispatch!(
ssz_generic_test,
(&self.path),
BitList,
<>,
[limit => typenum]
)?;
}
"bitvector" => {
let length = parts[1];
type_dispatch!(
ssz_generic_test,
(&self.path),
BitVector,
<>,
[length => typenum]
)?;
}
"boolean" => {
ssz_generic_test::<bool>(&self.path)?;
}
"uints" => {
let type_name = "uint".to_owned() + parts[1];
type_dispatch!(
ssz_generic_test,
(&self.path),
_,
<>,
[type_name.as_str() => primitive_type]
)?;
}
"containers" => {
let type_name = parts[0];
type_dispatch!(
ssz_generic_test,
(&self.path),
_,
<>,
[type_name => test_container]
)?;
}
_ => panic!("unsupported handler: {}", self.handler_name),
}
Ok(())
}
}
fn ssz_generic_test<T: SszStaticType>(path: &Path) -> Result<(), Error> {
let meta_path = path.join("meta.yaml");
let meta: Option<Metadata> = if meta_path.is_file() {
Some(yaml_decode_file(&meta_path)?)
} else {
None
};
let serialized = fs::read(&path.join("serialized.ssz")).expect("serialized.ssz exists");
let value_path = path.join("value.yaml");
let value: Option<T> = if value_path.is_file() {
Some(yaml_decode_file(&value_path)?)
} else {
None
};
// Valid
// TODO: signing root (annoying because of traits)
if let Some(value) = value {
check_serialization(&value, &serialized)?;
if let Some(ref meta) = meta {
check_tree_hash(&meta.root, value.tree_hash_root().as_bytes())?;
}
}
// Invalid
else if let Ok(decoded) = T::from_ssz_bytes(&serialized) {
return Err(Error::DidntFail(format!(
"Decoded invalid bytes into: {:?}",
decoded
)));
}
Ok(())
}
// Containers for SSZ generic tests
#[derive(Debug, Clone, Default, PartialEq, Decode, Encode, TreeHash, Deserialize)]
struct SingleFieldTestStruct {
A: u8,
}
#[derive(Debug, Clone, Default, PartialEq, Decode, Encode, TreeHash, Deserialize)]
struct SmallTestStruct {
A: u16,
B: u16,
}
#[derive(Debug, Clone, Default, PartialEq, Decode, Encode, TreeHash, Deserialize)]
struct FixedTestStruct {
A: u8,
B: u64,
C: u32,
}
#[derive(Debug, Clone, Default, PartialEq, Decode, Encode, TreeHash, Deserialize)]
struct VarTestStruct {
A: u16,
B: VariableList<u16, U1024>,
C: u8,
}
#[derive(Debug, Clone, Default, PartialEq, Decode, Encode, TreeHash, Deserialize)]
struct ComplexTestStruct {
A: u16,
B: VariableList<u16, U128>,
C: u8,
#[serde(deserialize_with = "byte_list_from_hex_str")]
D: VariableList<u8, U256>,
E: VarTestStruct,
F: FixedVector<FixedTestStruct, U4>,
G: FixedVector<VarTestStruct, U2>,
}
#[derive(Debug, Clone, PartialEq, Decode, Encode, TreeHash, Deserialize)]
struct BitsStruct {
A: BitList<U5>,
B: BitVector<U2>,
C: BitVector<U1>,
D: BitList<U6>,
E: BitVector<U8>,
}
fn byte_list_from_hex_str<'de, D, N: Unsigned>(
deserializer: D,
) -> Result<VariableList<u8, N>, D::Error>
where
D: Deserializer<'de>,
{
let s: String = serde::de::Deserialize::deserialize(deserializer)?;
let decoded: Vec<u8> = hex::decode(&s.as_str()[2..]).map_err(D::Error::custom)?;
if decoded.len() > N::to_usize() {
Err(D::Error::custom(format!(
"Too many values for list, got: {}, limit: {}",
decoded.len(),
N::to_usize()
)))
} else {
Ok(decoded.into())
}
}

View File

@@ -0,0 +1,105 @@
use super::*;
use crate::case_result::compare_result;
use crate::cases::common::SszStaticType;
use crate::decode::yaml_decode_file;
use cached_tree_hash::{CacheArena, CachedTreeHash};
use serde_derive::Deserialize;
use std::fs;
use std::marker::PhantomData;
use types::Hash256;
#[derive(Debug, Clone, Deserialize)]
struct SszStaticRoots {
root: String,
signing_root: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SszStatic<T> {
roots: SszStaticRoots,
serialized: Vec<u8>,
value: T,
}
#[derive(Debug, Clone)]
pub struct SszStaticTHC<T, C> {
roots: SszStaticRoots,
serialized: Vec<u8>,
value: T,
_phantom: PhantomData<C>,
}
fn load_from_dir<T: SszStaticType>(path: &Path) -> Result<(SszStaticRoots, Vec<u8>, T), Error> {
let roots = yaml_decode_file(&path.join("roots.yaml"))?;
let serialized = fs::read(&path.join("serialized.ssz")).expect("serialized.ssz exists");
let value = yaml_decode_file(&path.join("value.yaml"))?;
Ok((roots, serialized, value))
}
impl<T: SszStaticType> LoadCase for SszStatic<T> {
fn load_from_dir(path: &Path) -> Result<Self, Error> {
load_from_dir(path).map(|(roots, serialized, value)| Self {
roots,
serialized,
value,
})
}
}
impl<T: SszStaticType + CachedTreeHash<C>, C: Debug + Sync> LoadCase for SszStaticTHC<T, C> {
fn load_from_dir(path: &Path) -> Result<Self, Error> {
load_from_dir(path).map(|(roots, serialized, value)| Self {
roots,
serialized,
value,
_phantom: PhantomData,
})
}
}
pub fn check_serialization<T: SszStaticType>(value: &T, serialized: &[u8]) -> Result<(), Error> {
// Check serialization
let serialized_result = value.as_ssz_bytes();
compare_result::<usize, Error>(&Ok(value.ssz_bytes_len()), &Some(serialized.len()))?;
compare_result::<Vec<u8>, Error>(&Ok(serialized_result), &Some(serialized.to_vec()))?;
// Check deserialization
let deserialized_result = T::from_ssz_bytes(serialized);
compare_result(&deserialized_result, &Some(value.clone()))?;
Ok(())
}
pub fn check_tree_hash(expected_str: &str, actual_root: &[u8]) -> Result<(), Error> {
let expected_root = hex::decode(&expected_str[2..])
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
let expected_root = Hash256::from_slice(&expected_root);
let tree_hash_root = Hash256::from_slice(actual_root);
compare_result::<Hash256, Error>(&Ok(tree_hash_root), &Some(expected_root))
}
impl<T: SszStaticType> Case for SszStatic<T> {
fn result(&self, _case_index: usize) -> Result<(), Error> {
check_serialization(&self.value, &self.serialized)?;
check_tree_hash(&self.roots.root, self.value.tree_hash_root().as_bytes())?;
Ok(())
}
}
impl<T: SszStaticType + CachedTreeHash<C>, C: Debug + Sync> Case for SszStaticTHC<T, C> {
fn result(&self, _case_index: usize) -> Result<(), Error> {
check_serialization(&self.value, &self.serialized)?;
check_tree_hash(&self.roots.root, self.value.tree_hash_root().as_bytes())?;
let arena = &mut CacheArena::default();
let mut cache = self.value.new_tree_hash_cache(arena);
let cached_tree_hash_root = self
.value
.recalculate_tree_hash_root(arena, &mut cache)
.unwrap();
check_tree_hash(&self.roots.root, cached_tree_hash_root.as_bytes())?;
Ok(())
}
}

View File

@@ -0,0 +1,31 @@
use super::*;
use std::fs;
use std::path::Path;
pub fn yaml_decode<T: serde::de::DeserializeOwned>(string: &str) -> Result<T, Error> {
serde_yaml::from_str(string).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))
}
pub fn yaml_decode_file<T: serde::de::DeserializeOwned>(path: &Path) -> Result<T, Error> {
fs::read_to_string(path)
.map_err(|e| {
Error::FailedToParseTest(format!("Unable to load {}: {:?}", path.display(), e))
})
.and_then(|s| yaml_decode(&s))
}
pub fn ssz_decode_file<T: ssz::Decode>(path: &Path) -> Result<T, Error> {
fs::read(path)
.map_err(|e| {
Error::FailedToParseTest(format!("Unable to load {}: {:?}", path.display(), e))
})
.and_then(|s| {
T::from_ssz_bytes(&s).map_err(|e| {
Error::FailedToParseTest(format!(
"Unable to parse SSZ at {}: {:?}",
path.display(),
e
))
})
})
}

View File

@@ -0,0 +1,41 @@
#[derive(Debug, PartialEq, Clone)]
pub enum Error {
/// The value in the test didn't match our value.
NotEqual(String),
/// The test specified a failure and we did not experience one.
DidntFail(String),
/// Failed to parse the test (internal error).
FailedToParseTest(String),
/// Skipped the test because the BLS setting was mismatched.
SkippedBls,
/// Skipped the test because it's known to fail.
SkippedKnownFailure,
}
impl Error {
pub fn name(&self) -> &str {
match self {
Error::NotEqual(_) => "NotEqual",
Error::DidntFail(_) => "DidntFail",
Error::FailedToParseTest(_) => "FailedToParseTest",
Error::SkippedBls => "SkippedBls",
Error::SkippedKnownFailure => "SkippedKnownFailure",
}
}
pub fn message(&self) -> &str {
match self {
Error::NotEqual(m) => m.as_str(),
Error::DidntFail(m) => m.as_str(),
Error::FailedToParseTest(m) => m.as_str(),
_ => self.name(),
}
}
pub fn is_skipped(&self) -> bool {
match self {
Error::SkippedBls | Error::SkippedKnownFailure => true,
_ => false,
}
}
}

View File

@@ -0,0 +1,294 @@
use crate::cases::{self, Case, Cases, EpochTransition, LoadCase, Operation};
use crate::type_name;
use crate::type_name::TypeName;
use cached_tree_hash::CachedTreeHash;
use std::fmt::Debug;
use std::fs;
use std::marker::PhantomData;
use std::path::PathBuf;
use types::EthSpec;
pub trait Handler {
type Case: Case + LoadCase;
fn config_name() -> &'static str {
"general"
}
fn fork_name() -> &'static str {
"phase0"
}
fn runner_name() -> &'static str;
fn handler_name() -> String;
fn run() {
let handler_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("eth2.0-spec-tests")
.join("tests")
.join(Self::config_name())
.join(Self::fork_name())
.join(Self::runner_name())
.join(Self::handler_name());
// Iterate through test suites
let test_cases = fs::read_dir(&handler_path)
.expect("handler dir exists")
.flat_map(|entry| {
entry
.ok()
.filter(|e| e.file_type().map(|ty| ty.is_dir()).unwrap_or(false))
})
.flat_map(|suite| fs::read_dir(suite.path()).expect("suite dir exists"))
.flat_map(Result::ok)
.map(|test_case_dir| {
let path = test_case_dir.path();
let case = Self::Case::load_from_dir(&path).expect("test should load");
(path, case)
})
.collect();
let results = Cases { test_cases }.test_results();
let name = format!("{}/{}", Self::runner_name(), Self::handler_name());
crate::results::assert_tests_pass(&name, &handler_path, &results);
}
}
macro_rules! bls_handler {
($runner_name: ident, $case_name:ident, $handler_name:expr) => {
pub struct $runner_name;
impl Handler for $runner_name {
type Case = cases::$case_name;
fn runner_name() -> &'static str {
"bls"
}
fn handler_name() -> String {
$handler_name.into()
}
}
};
}
bls_handler!(BlsAggregateSigsHandler, BlsAggregateSigs, "aggregate");
bls_handler!(BlsSignMsgHandler, BlsSign, "sign");
bls_handler!(BlsVerifyMsgHandler, BlsVerify, "verify");
bls_handler!(
BlsAggregateVerifyHandler,
BlsAggregateVerify,
"aggregate_verify"
);
bls_handler!(
BlsFastAggregateVerifyHandler,
BlsFastAggregateVerify,
"fast_aggregate_verify"
);
/// Handler for SSZ types.
pub struct SszStaticHandler<T, E>(PhantomData<(T, E)>);
/// Handler for SSZ types that implement `CachedTreeHash`.
pub struct SszStaticTHCHandler<T, C, E>(PhantomData<(T, C, E)>);
impl<T, E> Handler for SszStaticHandler<T, E>
where
T: cases::SszStaticType + TypeName,
E: TypeName,
{
type Case = cases::SszStatic<T>;
fn config_name() -> &'static str {
E::name()
}
fn runner_name() -> &'static str {
"ssz_static"
}
fn handler_name() -> String {
T::name().into()
}
}
impl<T, C, E> Handler for SszStaticTHCHandler<T, C, E>
where
T: cases::SszStaticType + CachedTreeHash<C> + TypeName,
C: Debug + Sync,
E: TypeName,
{
type Case = cases::SszStaticTHC<T, C>;
fn config_name() -> &'static str {
E::name()
}
fn runner_name() -> &'static str {
"ssz_static"
}
fn handler_name() -> String {
T::name().into()
}
}
pub struct ShufflingHandler<E>(PhantomData<E>);
impl<E: EthSpec + TypeName> Handler for ShufflingHandler<E> {
type Case = cases::Shuffling<E>;
fn config_name() -> &'static str {
E::name()
}
fn runner_name() -> &'static str {
"shuffling"
}
fn handler_name() -> String {
"core".into()
}
}
pub struct SanityBlocksHandler<E>(PhantomData<E>);
impl<E: EthSpec + TypeName> Handler for SanityBlocksHandler<E> {
type Case = cases::SanityBlocks<E>;
fn config_name() -> &'static str {
E::name()
}
fn runner_name() -> &'static str {
"sanity"
}
fn handler_name() -> String {
"blocks".into()
}
}
pub struct SanitySlotsHandler<E>(PhantomData<E>);
impl<E: EthSpec + TypeName> Handler for SanitySlotsHandler<E> {
type Case = cases::SanitySlots<E>;
fn config_name() -> &'static str {
E::name()
}
fn runner_name() -> &'static str {
"sanity"
}
fn handler_name() -> String {
"slots".into()
}
}
pub struct EpochProcessingHandler<E, T>(PhantomData<(E, T)>);
impl<E: EthSpec + TypeName, T: EpochTransition<E>> Handler for EpochProcessingHandler<E, T> {
type Case = cases::EpochProcessing<E, T>;
fn config_name() -> &'static str {
E::name()
}
fn runner_name() -> &'static str {
"epoch_processing"
}
fn handler_name() -> String {
T::name().into()
}
}
pub struct GenesisValidityHandler<E>(PhantomData<E>);
impl<E: EthSpec + TypeName> Handler for GenesisValidityHandler<E> {
type Case = cases::GenesisValidity<E>;
fn config_name() -> &'static str {
E::name()
}
fn runner_name() -> &'static str {
"genesis"
}
fn handler_name() -> String {
"validity".into()
}
}
pub struct GenesisInitializationHandler<E>(PhantomData<E>);
impl<E: EthSpec + TypeName> Handler for GenesisInitializationHandler<E> {
type Case = cases::GenesisInitialization<E>;
fn config_name() -> &'static str {
E::name()
}
fn runner_name() -> &'static str {
"genesis"
}
fn handler_name() -> String {
"initialization".into()
}
}
pub struct OperationsHandler<E, O>(PhantomData<(E, O)>);
impl<E: EthSpec + TypeName, O: Operation<E>> Handler for OperationsHandler<E, O> {
type Case = cases::Operations<E, O>;
fn config_name() -> &'static str {
E::name()
}
fn runner_name() -> &'static str {
"operations"
}
fn handler_name() -> String {
O::handler_name()
}
}
pub struct SszGenericHandler<H>(PhantomData<H>);
impl<H: TypeName> Handler for SszGenericHandler<H> {
type Case = cases::SszGeneric;
fn config_name() -> &'static str {
"general"
}
fn runner_name() -> &'static str {
"ssz_generic"
}
fn handler_name() -> 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");

View File

@@ -0,0 +1,19 @@
use types::EthSpec;
pub use case_result::CaseResult;
pub use cases::Case;
pub use cases::{
FinalUpdates, JustificationAndFinalization, RegistryUpdates, RewardsAndPenalties, Slashings,
};
pub use error::Error;
pub use handler::*;
pub use type_name::TypeName;
mod bls_setting;
mod case_result;
mod cases;
mod decode;
mod error;
mod handler;
mod results;
mod type_name;

View File

@@ -0,0 +1,92 @@
use crate::case_result::CaseResult;
use crate::error::Error;
use std::path::Path;
pub fn assert_tests_pass(handler_name: &str, path: &Path, results: &[CaseResult]) {
let (failed, skipped_bls, skipped_known_failures) = categorize_results(results);
if failed.len() + skipped_known_failures.len() > 0 {
print_results(
handler_name,
&failed,
&skipped_bls,
&skipped_known_failures,
&results,
);
if !failed.is_empty() {
panic!("Tests failed (see above)");
}
} else {
println!("Passed {} tests in {}", results.len(), path.display());
}
}
pub fn categorize_results(
results: &[CaseResult],
) -> (Vec<&CaseResult>, Vec<&CaseResult>, Vec<&CaseResult>) {
let mut failed = vec![];
let mut skipped_bls = vec![];
let mut skipped_known_failures = vec![];
for case in results {
match case.result.as_ref().err() {
Some(Error::SkippedBls) => skipped_bls.push(case),
Some(Error::SkippedKnownFailure) => skipped_known_failures.push(case),
Some(_) => failed.push(case),
None => (),
}
}
(failed, skipped_bls, skipped_known_failures)
}
pub fn print_results(
handler_name: &str,
failed: &[&CaseResult],
skipped_bls: &[&CaseResult],
skipped_known_failures: &[&CaseResult],
results: &[CaseResult],
) {
println!("--------------------------------------------------");
println!(
"Test {}",
if failed.is_empty() {
"Result"
} else {
"Failure"
}
);
println!("Title: {}", handler_name);
println!(
"{} tests, {} failed, {} skipped (known failure), {} skipped (bls), {} passed. (See below for errors)",
results.len(),
failed.len(),
skipped_known_failures.len(),
skipped_bls.len(),
results.len() - skipped_bls.len() - skipped_known_failures.len() - failed.len()
);
println!();
for case in skipped_known_failures {
println!("-------");
println!(
"case ({}) from {} skipped because it's a known failure",
case.desc,
case.path.display()
);
}
for failure in failed {
let error = failure.result.clone().unwrap_err();
println!("-------");
println!(
"case {} ({}) from {} failed with {}:",
failure.case_index,
failure.desc,
failure.path.display(),
error.name()
);
println!("{}", error.message());
}
println!();
}

View File

@@ -0,0 +1,57 @@
//! Mapping from types to canonical string identifiers used in testing.
use types::*;
pub trait TypeName {
fn name() -> &'static str;
}
#[macro_export]
macro_rules! type_name {
($typ:ident) => {
type_name!($typ, stringify!($typ));
};
($typ:ident, $name:expr) => {
impl TypeName for $typ {
fn name() -> &'static str {
$name
}
}
};
}
#[macro_export]
macro_rules! type_name_generic {
($typ:ident) => {
type_name_generic!($typ, stringify!($typ));
};
($typ:ident, $name:expr) => {
impl<E: EthSpec> TypeName for $typ<E> {
fn name() -> &'static str {
$name
}
}
};
}
type_name!(MinimalEthSpec, "minimal");
type_name!(MainnetEthSpec, "mainnet");
type_name_generic!(Attestation);
type_name!(AttestationData);
type_name_generic!(AttesterSlashing);
type_name_generic!(BeaconBlock);
type_name_generic!(BeaconBlockBody);
type_name!(BeaconBlockHeader);
type_name_generic!(BeaconState);
type_name!(Checkpoint);
type_name!(Deposit);
type_name!(DepositData);
type_name!(Eth1Data);
type_name!(Fork);
type_name_generic!(HistoricalBatch);
type_name_generic!(IndexedAttestation);
type_name_generic!(PendingAttestation);
type_name!(ProposerSlashing);
type_name!(SignedVoluntaryExit);
type_name!(Validator);
type_name!(VoluntaryExit);