Altair consensus changes and refactors (#2279)

## Proposed Changes

Implement the consensus changes necessary for the upcoming Altair hard fork.

## Additional Info

This is quite a heavy refactor, with pivotal types like the `BeaconState` and `BeaconBlock` changing from structs to enums. This ripples through the whole codebase with field accesses changing to methods, e.g. `state.slot` => `state.slot()`.


Co-authored-by: realbigsean <seananderson33@gmail.com>
This commit is contained in:
Michael Sproul
2021-07-09 06:15:32 +00:00
parent 89361573d4
commit b4689e20c6
271 changed files with 9652 additions and 8444 deletions

View File

@@ -1 +1,2 @@
/eth2.0-spec-tests
.accessed_file_log.txt

View File

@@ -13,6 +13,8 @@ fake_crypto = ["bls/fake_crypto"]
[dependencies]
bls = { path = "../../crypto/bls", default-features = false }
compare_fields = { path = "../../common/compare_fields" }
compare_fields_derive = { path = "../../common/compare_fields_derive" }
derivative = "2.1.1"
ethereum-types = "0.9.2"
hex = "0.4.2"
rayon = "1.4.1"
@@ -28,3 +30,6 @@ cached_tree_hash = { path = "../../consensus/cached_tree_hash" }
state_processing = { path = "../../consensus/state_processing" }
swap_or_not_shuffle = { path = "../../consensus/swap_or_not_shuffle" }
types = { path = "../../consensus/types" }
snap = "1.0.1"
parking_lot = "0.11.0"
fs2 = "0.4.3"

View File

@@ -1,4 +1,4 @@
TESTS_TAG := v1.0.1
TESTS_TAG := v1.1.0-alpha.7
TESTS = general minimal mainnet
TARBALLS = $(patsubst %,%-$(TESTS_TAG).tar.gz,$(TESTS))

View File

@@ -0,0 +1,108 @@
#!/usr/bin/env python3
# The purpose of this script is to compare a list of file names that were accessed during testing
# against all the file names in the eth2.0-spec-tests repository. It then checks to see which files
# were not accessed and returns an error if any non-intentionally-ignored files are detected.
#
# The ultimate goal is to detect any accidentally-missed spec tests.
import os
import sys
# First argument should the path to a file which contains a list of accessed file names.
accessed_files_filename = sys.argv[1]
# Second argument should be the path to the eth2.0-spec-tests directory.
tests_dir_filename = sys.argv[2]
# If any of the file names found in the eth2.0-spec-tests directory *starts with* one of the
# following strings, we will assume they are to be ignored (i.e., we are purposefully *not* running
# the spec tests).
excluded_paths = [
# Configs from future phases
"tests/mainnet/config/custody_game.yaml",
"tests/mainnet/config/sharding.yaml",
"tests/mainnet/config/merge.yaml",
"tests/minimal/config/custody_game.yaml",
"tests/minimal/config/sharding.yaml",
"tests/minimal/config/merge.yaml",
# Merge tests
"tests/minimal/merge",
"tests/mainnet/merge",
# Eth1Block
#
# Intentionally omitted, as per https://github.com/sigp/lighthouse/issues/1835
"tests/minimal/phase0/ssz_static/Eth1Block/",
"tests/mainnet/phase0/ssz_static/Eth1Block/",
"tests/minimal/altair/ssz_static/Eth1Block/",
"tests/mainnet/altair/ssz_static/Eth1Block/",
# LightClientStore
"tests/minimal/altair/ssz_static/LightClientStore",
"tests/mainnet/altair/ssz_static/LightClientStore",
# LightClientUpdate
"tests/minimal/altair/ssz_static/LightClientUpdate",
"tests/mainnet/altair/ssz_static/LightClientUpdate",
# LightClientSnapshot
"tests/minimal/altair/ssz_static/LightClientSnapshot",
"tests/mainnet/altair/ssz_static/LightClientSnapshot",
# ContributionAndProof
"tests/minimal/altair/ssz_static/ContributionAndProof",
"tests/mainnet/altair/ssz_static/ContributionAndProof",
# SignedContributionAndProof
"tests/minimal/altair/ssz_static/SignedContributionAndProof",
"tests/mainnet/altair/ssz_static/SignedContributionAndProof",
# SyncCommitteeContribution
"tests/minimal/altair/ssz_static/SyncCommitteeContribution",
"tests/mainnet/altair/ssz_static/SyncCommitteeContribution",
# SyncCommitteeMessage
"tests/minimal/altair/ssz_static/SyncCommitteeMessage",
"tests/mainnet/altair/ssz_static/SyncCommitteeMessage",
# SyncCommitteeSigningData
"tests/minimal/altair/ssz_static/SyncCommitteeSigningData",
"tests/mainnet/altair/ssz_static/SyncCommitteeSigningData",
# SyncAggregatorSelectionData
"tests/minimal/altair/ssz_static/SyncAggregatorSelectionData",
"tests/mainnet/altair/ssz_static/SyncAggregatorSelectionData",
# Fork choice
"tests/mainnet/phase0/fork_choice",
"tests/minimal/phase0/fork_choice",
"tests/mainnet/altair/fork_choice",
"tests/minimal/altair/fork_choice",
]
def normalize_path(path):
return path.split("eth2.0-spec-tests/", )[1]
# Determine the list of filenames which were accessed during tests.
passed = set()
for line in open(accessed_files_filename, 'r').readlines():
file = normalize_path(line.strip().strip('"'))
passed.add(file)
missed = set()
accessed_files = 0
excluded_files = 0
# Iterate all files in the tests directory, ensure that all files were either accessed
# or intentionally missed.
for root, dirs, files in os.walk(tests_dir_filename):
for name in files:
name = normalize_path(os.path.join(root, name))
if name not in passed:
excluded = False
for excluded_path in excluded_paths:
if name.startswith(excluded_path):
excluded = True
break
if excluded:
excluded_files += 1
else:
print(name)
missed.add(name)
else:
accessed_files += 1
# Exit with an error if there were any files missed.
assert len(missed) == 0, "{} missed files".format(len(missed))
print("Accessed {} files ({} intentionally excluded)".format(accessed_files, excluded_files))

View File

@@ -37,8 +37,8 @@ pub fn compare_beacon_state_results_without_caches<T: EthSpec, E: Debug>(
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();
result.drop_all_caches().unwrap();
expected.drop_all_caches().unwrap();
}
compare_result_detailed(&result, &expected)
@@ -94,7 +94,7 @@ where
(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 {:?}",
"Got {:?} | Expected {}",
e,
fmt_val(expected)
))),
@@ -106,7 +106,7 @@ where
Ok(())
} else {
Err(Error::NotEqual(format!(
"Got {:?} | Expected {:?}",
"Got {} | Expected {}",
fmt_val(result),
fmt_val(expected)
)))

View File

@@ -2,6 +2,7 @@ use super::*;
use rayon::prelude::*;
use std::fmt::Debug;
use std::path::{Path, PathBuf};
use types::ForkName;
mod bls_aggregate_sigs;
mod bls_aggregate_verify;
@@ -10,14 +11,17 @@ mod bls_sign_msg;
mod bls_verify_msg;
mod common;
mod epoch_processing;
mod fork;
mod genesis_initialization;
mod genesis_validity;
mod operations;
mod rewards;
mod sanity_blocks;
mod sanity_slots;
mod shuffling;
mod ssz_generic;
mod ssz_static;
mod transition;
pub use bls_aggregate_sigs::*;
pub use bls_aggregate_verify::*;
@@ -26,18 +30,21 @@ pub use bls_sign_msg::*;
pub use bls_verify_msg::*;
pub use common::SszStaticType;
pub use epoch_processing::*;
pub use fork::ForkTest;
pub use genesis_initialization::*;
pub use genesis_validity::*;
pub use operations::*;
pub use rewards::RewardsTest;
pub use sanity_blocks::*;
pub use sanity_slots::*;
pub use shuffling::*;
pub use ssz_generic::*;
pub use ssz_static::*;
pub use transition::TransitionTest;
pub trait LoadCase: Sized {
/// Load the test case from a test case directory.
fn load_from_dir(_path: &Path) -> Result<Self, Error>;
fn load_from_dir(_path: &Path, _fork_name: ForkName) -> Result<Self, Error>;
}
pub trait Case: Debug + Sync {
@@ -48,11 +55,18 @@ pub trait Case: Debug + Sync {
"no description".to_string()
}
/// Whether or not this test exists for the given `fork_name`.
///
/// Returns `true` by default.
fn is_enabled_for_fork(_fork_name: ForkName) -> bool {
true
}
/// 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>;
fn result(&self, case_index: usize, fork_name: ForkName) -> Result<(), Error>;
}
#[derive(Debug)]
@@ -61,11 +75,11 @@ pub struct Cases<T> {
}
impl<T: Case> Cases<T> {
pub fn test_results(&self) -> Vec<CaseResult> {
pub fn test_results(&self, fork_name: ForkName) -> Vec<CaseResult> {
self.test_cases
.into_par_iter()
.enumerate()
.map(|(i, (ref path, ref tc))| CaseResult::new(i, path, tc, tc.result(i)))
.map(|(i, (ref path, ref tc))| CaseResult::new(i, path, tc, tc.result(i, fork_name)))
.collect()
}
}

View File

@@ -13,7 +13,7 @@ pub struct BlsAggregateSigs {
impl BlsCase for BlsAggregateSigs {}
impl Case for BlsAggregateSigs {
fn result(&self, _case_index: usize) -> Result<(), Error> {
fn result(&self, _case_index: usize, _fork_name: ForkName) -> Result<(), Error> {
let mut aggregate_signature = AggregateSignature::infinity();
for key_str in &self.input {

View File

@@ -21,7 +21,7 @@ pub struct BlsAggregateVerify {
impl BlsCase for BlsAggregateVerify {}
impl Case for BlsAggregateVerify {
fn result(&self, _case_index: usize) -> Result<(), Error> {
fn result(&self, _case_index: usize, _fork_name: ForkName) -> Result<(), Error> {
let messages = self
.input
.messages

View File

@@ -23,7 +23,7 @@ pub struct BlsFastAggregateVerify {
impl BlsCase for BlsFastAggregateVerify {}
impl Case for BlsFastAggregateVerify {
fn result(&self, _case_index: usize) -> Result<(), Error> {
fn result(&self, _case_index: usize, _fork_name: ForkName) -> Result<(), Error> {
let message = Hash256::from_slice(
&hex::decode(&self.input.message[2..])
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?,

View File

@@ -20,7 +20,7 @@ pub struct BlsSign {
impl BlsCase for BlsSign {}
impl Case for BlsSign {
fn result(&self, _case_index: usize) -> Result<(), Error> {
fn result(&self, _case_index: usize, _fork_name: ForkName) -> 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)))?;

View File

@@ -22,7 +22,7 @@ pub struct BlsVerify {
impl BlsCase for BlsVerify {}
impl Case for BlsVerify {
fn result(&self, _case_index: usize) -> Result<(), Error> {
fn result(&self, _case_index: usize, _fork_name: ForkName) -> Result<(), Error> {
let message = hex::decode(&self.input.message[2..])
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;

View File

@@ -2,18 +2,19 @@ use crate::cases::LoadCase;
use crate::decode::yaml_decode_file;
use crate::error::Error;
use serde_derive::Deserialize;
use ssz::{Decode, Encode};
use ssz::Encode;
use ssz_derive::{Decode, Encode};
use std::convert::TryFrom;
use std::fmt::Debug;
use std::path::Path;
use tree_hash::TreeHash;
use types::ForkName;
/// 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> {
fn load_from_dir(path: &Path, _fork_name: ForkName) -> Result<Self, Error> {
yaml_decode_file(&path.join("data.yaml"))
}
}
@@ -60,13 +61,21 @@ macro_rules! uint_wrapper {
uint_wrapper!(TestU128, ethereum_types::U128);
uint_wrapper!(TestU256, ethereum_types::U256);
/// Trait alias for all deez bounds
/// Trait for types that can be used in SSZ static tests.
pub trait SszStaticType:
serde::de::DeserializeOwned + Decode + Encode + TreeHash + Clone + PartialEq + Debug + Sync
serde::de::DeserializeOwned + Encode + TreeHash + Clone + PartialEq + Debug + Sync
{
}
impl<T> SszStaticType for T where
T: serde::de::DeserializeOwned + Decode + Encode + TreeHash + Clone + PartialEq + Debug + Sync
T: serde::de::DeserializeOwned + Encode + TreeHash + Clone + PartialEq + Debug + Sync
{
}
/// 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,
}
}

View File

@@ -1,18 +1,22 @@
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::decode::{ssz_decode_state, yaml_decode_file};
use crate::type_name;
use crate::type_name::TypeName;
use serde_derive::Deserialize;
use state_processing::per_epoch_processing::validator_statuses::ValidatorStatuses;
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,
altair, base,
effective_balance_updates::process_effective_balance_updates,
historical_roots_update::process_historical_roots_update,
process_registry_updates, process_slashings,
resets::{process_eth1_data_reset, process_randao_mixes_reset, process_slashings_reset},
};
use state_processing::EpochProcessingError;
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use types::{BeaconState, ChainSpec, EthSpec};
use types::{BeaconState, ChainSpec, EthSpec, ForkName};
#[derive(Debug, Clone, Default, Deserialize)]
pub struct Metadata {
@@ -44,7 +48,23 @@ pub struct RegistryUpdates;
#[derive(Debug)]
pub struct Slashings;
#[derive(Debug)]
pub struct FinalUpdates;
pub struct Eth1DataReset;
#[derive(Debug)]
pub struct EffectiveBalanceUpdates;
#[derive(Debug)]
pub struct SlashingsReset;
#[derive(Debug)]
pub struct RandaoMixesReset;
#[derive(Debug)]
pub struct HistoricalRootsUpdate;
#[derive(Debug)]
pub struct ParticipationRecordUpdates;
#[derive(Debug)]
pub struct SyncCommitteeUpdates;
#[derive(Debug)]
pub struct InactivityUpdates;
#[derive(Debug)]
pub struct ParticipationFlagUpdates;
type_name!(
JustificationAndFinalization,
@@ -53,21 +73,43 @@ type_name!(
type_name!(RewardsAndPenalties, "rewards_and_penalties");
type_name!(RegistryUpdates, "registry_updates");
type_name!(Slashings, "slashings");
type_name!(FinalUpdates, "final_updates");
type_name!(Eth1DataReset, "eth1_data_reset");
type_name!(EffectiveBalanceUpdates, "effective_balance_updates");
type_name!(SlashingsReset, "slashings_reset");
type_name!(RandaoMixesReset, "randao_mixes_reset");
type_name!(HistoricalRootsUpdate, "historical_roots_update");
type_name!(ParticipationRecordUpdates, "participation_record_updates");
type_name!(SyncCommitteeUpdates, "sync_committee_updates");
type_name!(InactivityUpdates, "inactivity_updates");
type_name!(ParticipationFlagUpdates, "participation_flag_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)
match state {
BeaconState::Base(_) => {
let mut validator_statuses = ValidatorStatuses::new(state, spec)?;
validator_statuses.process_attestations(state)?;
base::process_justification_and_finalization(
state,
&validator_statuses.total_balances,
spec,
)
}
BeaconState::Altair(_) => altair::process_justification_and_finalization(state, spec),
}
}
}
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)
match state {
BeaconState::Base(_) => {
let mut validator_statuses = ValidatorStatuses::new(state, spec)?;
validator_statuses.process_attestations(state)?;
base::process_rewards_and_penalties(state, &mut validator_statuses, spec)
}
BeaconState::Altair(_) => altair::process_rewards_and_penalties(state, spec),
}
}
}
@@ -79,35 +121,110 @@ impl<E: EthSpec> EpochTransition<E> for RegistryUpdates {
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,
)?;
match state {
BeaconState::Base(_) => {
let mut validator_statuses = ValidatorStatuses::new(&state, spec)?;
validator_statuses.process_attestations(&state)?;
process_slashings(
state,
validator_statuses.total_balances.current_epoch(),
spec.proportional_slashing_multiplier,
spec,
)?;
}
BeaconState::Altair(_) => {
process_slashings(
state,
state.get_total_active_balance(spec)?,
spec.proportional_slashing_multiplier_altair,
spec,
)?;
}
};
Ok(())
}
}
impl<E: EthSpec> EpochTransition<E> for FinalUpdates {
impl<E: EthSpec> EpochTransition<E> for Eth1DataReset {
fn run(state: &mut BeaconState<E>, _spec: &ChainSpec) -> Result<(), EpochProcessingError> {
process_eth1_data_reset(state)
}
}
impl<E: EthSpec> EpochTransition<E> for EffectiveBalanceUpdates {
fn run(state: &mut BeaconState<E>, spec: &ChainSpec) -> Result<(), EpochProcessingError> {
process_final_updates(state, spec)
process_effective_balance_updates(state, spec)
}
}
impl<E: EthSpec> EpochTransition<E> for SlashingsReset {
fn run(state: &mut BeaconState<E>, _spec: &ChainSpec) -> Result<(), EpochProcessingError> {
process_slashings_reset(state)
}
}
impl<E: EthSpec> EpochTransition<E> for RandaoMixesReset {
fn run(state: &mut BeaconState<E>, _spec: &ChainSpec) -> Result<(), EpochProcessingError> {
process_randao_mixes_reset(state)
}
}
impl<E: EthSpec> EpochTransition<E> for HistoricalRootsUpdate {
fn run(state: &mut BeaconState<E>, _spec: &ChainSpec) -> Result<(), EpochProcessingError> {
process_historical_roots_update(state)
}
}
impl<E: EthSpec> EpochTransition<E> for ParticipationRecordUpdates {
fn run(state: &mut BeaconState<E>, _spec: &ChainSpec) -> Result<(), EpochProcessingError> {
if let BeaconState::Base(_) = state {
base::process_participation_record_updates(state)
} else {
Ok(())
}
}
}
impl<E: EthSpec> EpochTransition<E> for SyncCommitteeUpdates {
fn run(state: &mut BeaconState<E>, spec: &ChainSpec) -> Result<(), EpochProcessingError> {
match state {
BeaconState::Base(_) => Ok(()),
BeaconState::Altair(_) => altair::process_sync_committee_updates(state, spec),
}
}
}
impl<E: EthSpec> EpochTransition<E> for InactivityUpdates {
fn run(state: &mut BeaconState<E>, spec: &ChainSpec) -> Result<(), EpochProcessingError> {
match state {
BeaconState::Base(_) => Ok(()),
BeaconState::Altair(_) => altair::process_inactivity_updates(state, spec),
}
}
}
impl<E: EthSpec> EpochTransition<E> for ParticipationFlagUpdates {
fn run(state: &mut BeaconState<E>, _: &ChainSpec) -> Result<(), EpochProcessingError> {
match state {
BeaconState::Base(_) => Ok(()),
BeaconState::Altair(_) => altair::process_participation_flag_updates(state),
}
}
}
impl<E: EthSpec, T: EpochTransition<E>> LoadCase for EpochProcessing<E, T> {
fn load_from_dir(path: &Path) -> Result<Self, Error> {
fn load_from_dir(path: &Path, fork_name: ForkName) -> Result<Self, Error> {
let spec = &testing_spec::<E>(fork_name);
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 pre = ssz_decode_state(&path.join("pre.ssz_snappy"), spec)?;
let post_file = path.join("post.ssz_snappy");
let post = if post_file.is_file() {
Some(ssz_decode_file(&post_file)?)
Some(ssz_decode_state(&post_file, spec)?)
} else {
None
};
@@ -130,11 +247,25 @@ impl<E: EthSpec, T: EpochTransition<E>> Case for EpochProcessing<E, T> {
.unwrap_or_else(String::new)
}
fn result(&self, _case_index: usize) -> Result<(), Error> {
fn is_enabled_for_fork(fork_name: ForkName) -> bool {
match fork_name {
// No Altair tests for genesis fork.
ForkName::Base => {
T::name() != "sync_committee_updates"
&& T::name() != "inactivity_updates"
&& T::name() != "participation_flag_updates"
}
ForkName::Altair => true,
}
}
fn result(&self, _case_index: usize, fork_name: ForkName) -> 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();
let spec = &testing_spec::<E>(fork_name);
let mut result = (|| {
// Processing requires the committee caches.

View File

@@ -0,0 +1,67 @@
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_derive::Deserialize;
use state_processing::upgrade::upgrade_to_altair;
use types::{BeaconState, ForkName};
#[derive(Debug, Clone, Default, Deserialize)]
pub struct Metadata {
pub fork: String,
}
impl Metadata {
fn fork_name(&self) -> ForkName {
self.fork.parse().unwrap()
}
}
#[derive(Debug)]
pub struct ForkTest<E: EthSpec> {
pub metadata: Metadata,
pub pre: BeaconState<E>,
pub post: BeaconState<E>,
}
impl<E: EthSpec> LoadCase for ForkTest<E> {
fn load_from_dir(path: &Path, fork_name: ForkName) -> Result<Self, Error> {
let metadata: Metadata = yaml_decode_file(&path.join("meta.yaml"))?;
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 = ssz_decode_state(&path.join("pre.ssz_snappy"), pre_spec)?;
// Decode post-state with target fork.
let post_spec = &fork_name.make_genesis_spec(E::default_spec());
let post = ssz_decode_state(&path.join("post.ssz_snappy"), post_spec)?;
Ok(Self {
metadata,
pre,
post,
})
}
}
impl<E: EthSpec> Case for ForkTest<E> {
fn is_enabled_for_fork(fork_name: ForkName) -> bool {
// Upgrades exist targeting all forks except phase0/base.
// Fork tests also need BLS.
cfg!(not(feature = "fake_crypto")) && fork_name != ForkName::Base
}
fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> {
let mut result_state = self.pre.clone();
let mut expected = Some(self.post.clone());
let spec = &E::default_spec();
let mut result = match fork_name {
ForkName::Altair => upgrade_to_altair(&mut result_state, spec).map(|_| result_state),
_ => panic!("unknown fork: {:?}", fork_name),
};
compare_beacon_state_results_without_caches(&mut result, &mut expected)
}
}

View File

@@ -1,16 +1,22 @@
use super::*;
use crate::case_result::compare_beacon_state_results_without_caches;
use crate::decode::{ssz_decode_file, yaml_decode_file};
use crate::decode::{ssz_decode_file, ssz_decode_state, 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};
use types::{BeaconState, Deposit, EthSpec, ForkName, Hash256};
#[derive(Debug, Clone, Deserialize)]
struct Metadata {
deposits_count: usize,
}
#[derive(Debug, Clone, Deserialize)]
struct Eth1 {
eth1_block_hash: Hash256,
eth1_timestamp: u64,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(bound = "E: EthSpec")]
pub struct GenesisInitialization<E: EthSpec> {
@@ -22,17 +28,20 @@ pub struct GenesisInitialization<E: EthSpec> {
}
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"))?;
fn load_from_dir(path: &Path, fork_name: ForkName) -> Result<Self, Error> {
let Eth1 {
eth1_block_hash,
eth1_timestamp,
} = yaml_decode_file(&path.join("eth1.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);
let filename = format!("deposits_{}.ssz_snappy", i);
ssz_decode_file(&path.join(filename))
})
.collect::<Result<_, _>>()?;
let state = ssz_decode_file(&path.join("state.ssz"))?;
let spec = &testing_spec::<E>(fork_name);
let state = ssz_decode_state(&path.join("state.ssz_snappy"), spec)?;
Ok(Self {
path: path.into(),
@@ -45,8 +54,13 @@ impl<E: EthSpec> LoadCase for GenesisInitialization<E> {
}
impl<E: EthSpec> Case for GenesisInitialization<E> {
fn result(&self, _case_index: usize) -> Result<(), Error> {
let spec = &E::default_spec();
fn is_enabled_for_fork(fork_name: ForkName) -> bool {
// Altair genesis and later requires real crypto.
fork_name == ForkName::Base || cfg!(not(feature = "fake_crypto"))
}
fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> {
let spec = &testing_spec::<E>(fork_name);
let mut result = initialize_beacon_state_from_eth1(
self.eth1_block_hash,

View File

@@ -1,29 +1,46 @@
use super::*;
use crate::decode::{ssz_decode_file, yaml_decode_file};
use crate::decode::{ssz_decode_state, yaml_decode_file};
use serde_derive::Deserialize;
use state_processing::is_valid_genesis_state;
use std::path::Path;
use types::{BeaconState, EthSpec};
use types::{BeaconState, EthSpec, ForkName};
#[derive(Debug, Clone, Deserialize)]
pub struct Metadata {
description: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(bound = "E: EthSpec")]
pub struct GenesisValidity<E: EthSpec> {
pub metadata: Option<Metadata>,
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"))?;
fn load_from_dir(path: &Path, fork_name: ForkName) -> Result<Self, Error> {
let spec = &testing_spec::<E>(fork_name);
let genesis = ssz_decode_state(&path.join("genesis.ssz_snappy"), spec)?;
let is_valid = yaml_decode_file(&path.join("is_valid.yaml"))?;
let meta_path = path.join("meta.yaml");
let metadata = if meta_path.exists() {
Some(yaml_decode_file(&meta_path)?)
} else {
None
};
Ok(Self { genesis, is_valid })
Ok(Self {
metadata,
genesis,
is_valid,
})
}
}
impl<E: EthSpec> Case for GenesisValidity<E> {
fn result(&self, _case_index: usize) -> Result<(), Error> {
let spec = &E::default_spec();
fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> {
let spec = &testing_spec::<E>(fork_name);
let is_valid = is_valid_genesis_state(&self.genesis, spec);

View File

@@ -1,20 +1,24 @@
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::decode::{ssz_decode_file, ssz_decode_file_with, ssz_decode_state, yaml_decode_file};
use crate::testing_spec;
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,
errors::BlockProcessingError,
process_block_header,
process_operations::{
altair, base, process_attester_slashings, process_deposits, process_exits,
process_proposer_slashings,
},
process_sync_aggregate, VerifySignatures,
};
use std::fmt::Debug;
use std::path::Path;
use types::{
Attestation, AttesterSlashing, BeaconBlock, BeaconState, ChainSpec, Deposit, EthSpec,
ProposerSlashing, SignedVoluntaryExit,
Attestation, AttesterSlashing, BeaconBlock, BeaconState, ChainSpec, Deposit, EthSpec, ForkName,
ProposerSlashing, SignedVoluntaryExit, SyncAggregate,
};
#[derive(Debug, Clone, Default, Deserialize)]
@@ -31,15 +35,21 @@ pub struct Operations<E: EthSpec, O: Operation<E>> {
pub post: Option<BeaconState<E>>,
}
pub trait Operation<E: EthSpec>: Decode + TypeName + Debug + Sync {
pub trait Operation<E: EthSpec>: TypeName + Debug + Sync + Sized {
fn handler_name() -> String {
Self::name().to_lowercase()
}
fn filename() -> String {
format!("{}.ssz", Self::handler_name())
format!("{}.ssz_snappy", Self::handler_name())
}
fn is_enabled_for_fork(_fork_name: ForkName) -> bool {
true
}
fn decode(path: &Path, spec: &ChainSpec) -> Result<Self, Error>;
fn apply_to(
&self,
state: &mut BeaconState<E>,
@@ -48,12 +58,23 @@ pub trait Operation<E: EthSpec>: Decode + TypeName + Debug + Sync {
}
impl<E: EthSpec> Operation<E> for Attestation<E> {
fn decode(path: &Path, _spec: &ChainSpec) -> Result<Self, Error> {
ssz_decode_file(path)
}
fn apply_to(
&self,
state: &mut BeaconState<E>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
process_attestations(state, &[self.clone()], VerifySignatures::True, spec)
match state {
BeaconState::Base(_) => {
base::process_attestations(state, &[self.clone()], VerifySignatures::True, spec)
}
BeaconState::Altair(_) => {
altair::process_attestation(state, self, 0, VerifySignatures::True, spec)
}
}
}
}
@@ -62,6 +83,10 @@ impl<E: EthSpec> Operation<E> for AttesterSlashing<E> {
"attester_slashing".into()
}
fn decode(path: &Path, _spec: &ChainSpec) -> Result<Self, Error> {
ssz_decode_file(path)
}
fn apply_to(
&self,
state: &mut BeaconState<E>,
@@ -72,6 +97,10 @@ impl<E: EthSpec> Operation<E> for AttesterSlashing<E> {
}
impl<E: EthSpec> Operation<E> for Deposit {
fn decode(path: &Path, _spec: &ChainSpec) -> Result<Self, Error> {
ssz_decode_file(path)
}
fn apply_to(
&self,
state: &mut BeaconState<E>,
@@ -86,6 +115,10 @@ impl<E: EthSpec> Operation<E> for ProposerSlashing {
"proposer_slashing".into()
}
fn decode(path: &Path, _spec: &ChainSpec) -> Result<Self, Error> {
ssz_decode_file(path)
}
fn apply_to(
&self,
state: &mut BeaconState<E>,
@@ -100,6 +133,10 @@ impl<E: EthSpec> Operation<E> for SignedVoluntaryExit {
"voluntary_exit".into()
}
fn decode(path: &Path, _spec: &ChainSpec) -> Result<Self, Error> {
ssz_decode_file(path)
}
fn apply_to(
&self,
state: &mut BeaconState<E>,
@@ -115,7 +152,11 @@ impl<E: EthSpec> Operation<E> for BeaconBlock<E> {
}
fn filename() -> String {
"block.ssz".into()
"block.ssz_snappy".into()
}
fn decode(path: &Path, spec: &ChainSpec) -> Result<Self, Error> {
ssz_decode_file_with(path, |bytes| BeaconBlock::from_ssz_bytes(bytes, spec))
}
fn apply_to(
@@ -123,12 +164,41 @@ impl<E: EthSpec> Operation<E> for BeaconBlock<E> {
state: &mut BeaconState<E>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
Ok(process_block_header(state, self, spec)?)
process_block_header(state, self.to_ref(), spec)?;
Ok(())
}
}
impl<E: EthSpec> Operation<E> for SyncAggregate<E> {
fn handler_name() -> String {
"sync_aggregate".into()
}
fn filename() -> String {
"sync_aggregate.ssz_snappy".into()
}
fn is_enabled_for_fork(fork_name: ForkName) -> bool {
fork_name != ForkName::Base
}
fn decode(path: &Path, _spec: &ChainSpec) -> Result<Self, Error> {
ssz_decode_file(path)
}
fn apply_to(
&self,
state: &mut BeaconState<E>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
let proposer_index = state.get_beacon_proposer_index(state.slot(), spec)? as u64;
process_sync_aggregate(state, self, proposer_index, spec)
}
}
impl<E: EthSpec, O: Operation<E>> LoadCase for Operations<E, O> {
fn load_from_dir(path: &Path) -> Result<Self, Error> {
fn load_from_dir(path: &Path, fork_name: ForkName) -> Result<Self, Error> {
let spec = &testing_spec::<E>(fork_name);
let metadata_path = path.join("meta.yaml");
let metadata: Metadata = if metadata_path.is_file() {
yaml_decode_file(&metadata_path)?
@@ -136,12 +206,12 @@ impl<E: EthSpec, O: Operation<E>> LoadCase for Operations<E, O> {
Metadata::default()
};
let pre = ssz_decode_file(&path.join("pre.ssz"))?;
let pre = ssz_decode_state(&path.join("pre.ssz_snappy"), spec)?;
// Check BLS setting here before SSZ deserialization, as most types require signatures
// to be valid.
let (operation, bls_error) = if metadata.bls_setting.unwrap_or_default().check().is_ok() {
match ssz_decode_file(&path.join(O::filename())) {
match O::decode(&path.join(O::filename()), spec) {
Ok(op) => (Some(op), None),
Err(Error::InvalidBLSInput(error)) => (None, Some(error)),
Err(e) => return Err(e),
@@ -149,12 +219,12 @@ impl<E: EthSpec, O: Operation<E>> LoadCase for Operations<E, O> {
} else {
(None, None)
};
let post_filename = path.join("post.ssz");
let post_filename = path.join("post.ssz_snappy");
let post = if post_filename.is_file() {
if let Some(bls_error) = bls_error {
panic!("input is unexpectedly invalid: {}", bls_error);
}
Some(ssz_decode_file(&post_filename)?)
Some(ssz_decode_state(&post_filename, spec)?)
} else {
None
};
@@ -176,8 +246,12 @@ impl<E: EthSpec, O: Operation<E>> Case for Operations<E, O> {
.unwrap_or_else(String::new)
}
fn result(&self, _case_index: usize) -> Result<(), Error> {
let spec = &E::default_spec();
fn is_enabled_for_fork(fork_name: ForkName) -> bool {
O::is_enabled_for_fork(fork_name)
}
fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> {
let spec = &testing_spec::<E>(fork_name);
let mut state = self.pre.clone();
let mut expected = self.post.clone();

View File

@@ -0,0 +1,206 @@
use super::*;
use crate::case_result::compare_result_detailed;
use crate::decode::{ssz_decode_file, ssz_decode_state, yaml_decode_file};
use compare_fields_derive::CompareFields;
use serde_derive::Deserialize;
use ssz_derive::{Decode, Encode};
use state_processing::per_epoch_processing::validator_statuses::ValidatorStatuses;
use state_processing::{
per_epoch_processing::{
altair::{self, rewards_and_penalties::get_flag_index_deltas},
base::{self, rewards_and_penalties::AttestationDelta},
Delta,
},
EpochProcessingError,
};
use std::path::{Path, PathBuf};
use types::{
consts::altair::{TIMELY_HEAD_FLAG_INDEX, TIMELY_SOURCE_FLAG_INDEX, TIMELY_TARGET_FLAG_INDEX},
BeaconState, EthSpec, ForkName,
};
#[derive(Debug, Clone, PartialEq, Decode, Encode, CompareFields)]
pub struct Deltas {
#[compare_fields(as_slice)]
rewards: Vec<u64>,
#[compare_fields(as_slice)]
penalties: Vec<u64>,
}
#[derive(Debug, Clone, PartialEq, Decode, Encode, CompareFields)]
pub struct AllDeltas {
source_deltas: Deltas,
target_deltas: Deltas,
head_deltas: Deltas,
inclusion_delay_deltas: Option<Deltas>,
inactivity_penalty_deltas: Deltas,
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct Metadata {
pub description: Option<String>,
}
#[derive(Debug, Clone)]
pub struct RewardsTest<E: EthSpec> {
pub path: PathBuf,
pub metadata: Metadata,
pub pre: BeaconState<E>,
pub deltas: AllDeltas,
}
/// Function that extracts a delta for a single component from an `AttestationDelta`.
type Accessor = fn(&AttestationDelta) -> &Delta;
fn load_optional_deltas_file(path: &Path) -> Result<Option<Deltas>, Error> {
let deltas = if path.is_file() {
Some(ssz_decode_file(&path)?)
} else {
None
};
Ok(deltas)
}
impl<E: EthSpec> LoadCase for RewardsTest<E> {
fn load_from_dir(path: &Path, fork_name: ForkName) -> Result<Self, Error> {
let spec = &testing_spec::<E>(fork_name);
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_state(&path.join("pre.ssz_snappy"), spec)?;
let source_deltas = ssz_decode_file(&path.join("source_deltas.ssz_snappy"))?;
let target_deltas = ssz_decode_file(&path.join("target_deltas.ssz_snappy"))?;
let head_deltas = ssz_decode_file(&path.join("head_deltas.ssz_snappy"))?;
let inclusion_delay_deltas =
load_optional_deltas_file(&path.join("inclusion_delay_deltas.ssz_snappy"))?;
let inactivity_penalty_deltas =
ssz_decode_file(&path.join("inactivity_penalty_deltas.ssz_snappy"))?;
let deltas = AllDeltas {
source_deltas,
target_deltas,
head_deltas,
inclusion_delay_deltas,
inactivity_penalty_deltas,
};
Ok(Self {
path: path.into(),
metadata,
pre,
deltas,
})
}
}
impl<E: EthSpec> Case for RewardsTest<E> {
fn description(&self) -> String {
self.metadata
.description
.clone()
.unwrap_or_else(String::new)
}
fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> {
let mut state = self.pre.clone();
let spec = &testing_spec::<E>(fork_name);
let deltas: Result<AllDeltas, EpochProcessingError> = (|| {
// Processing requires the committee caches.
state.build_all_committee_caches(spec)?;
if let BeaconState::Base(_) = state {
let mut validator_statuses = ValidatorStatuses::new(&state, spec)?;
validator_statuses.process_attestations(&state)?;
let deltas = base::rewards_and_penalties::get_attestation_deltas(
&state,
&validator_statuses,
spec,
)?;
Ok(convert_all_base_deltas(&deltas))
} else {
let total_active_balance = state.get_total_active_balance(spec)?;
let source_deltas = compute_altair_flag_deltas(
&state,
TIMELY_SOURCE_FLAG_INDEX,
total_active_balance,
spec,
)?;
let target_deltas = compute_altair_flag_deltas(
&state,
TIMELY_TARGET_FLAG_INDEX,
total_active_balance,
spec,
)?;
let head_deltas = compute_altair_flag_deltas(
&state,
TIMELY_HEAD_FLAG_INDEX,
total_active_balance,
spec,
)?;
let inactivity_penalty_deltas = compute_altair_inactivity_deltas(&state, spec)?;
Ok(AllDeltas {
source_deltas,
target_deltas,
head_deltas,
inclusion_delay_deltas: None,
inactivity_penalty_deltas,
})
}
})();
compare_result_detailed(&deltas, &Some(self.deltas.clone()))?;
Ok(())
}
}
fn convert_all_base_deltas(ad: &[AttestationDelta]) -> AllDeltas {
AllDeltas {
source_deltas: convert_base_deltas(ad, |d| &d.source_delta),
target_deltas: convert_base_deltas(ad, |d| &d.target_delta),
head_deltas: convert_base_deltas(ad, |d| &d.head_delta),
inclusion_delay_deltas: Some(convert_base_deltas(ad, |d| &d.inclusion_delay_delta)),
inactivity_penalty_deltas: convert_base_deltas(ad, |d| &d.inactivity_penalty_delta),
}
}
fn convert_base_deltas(attestation_deltas: &[AttestationDelta], accessor: Accessor) -> Deltas {
let (rewards, penalties) = attestation_deltas
.iter()
.map(accessor)
.map(|delta| (delta.rewards, delta.penalties))
.unzip();
Deltas { rewards, penalties }
}
fn compute_altair_flag_deltas<E: EthSpec>(
state: &BeaconState<E>,
flag_index: usize,
total_active_balance: u64,
spec: &ChainSpec,
) -> Result<Deltas, EpochProcessingError> {
let mut deltas = vec![Delta::default(); state.validators().len()];
get_flag_index_deltas(&mut deltas, state, flag_index, total_active_balance, spec)?;
Ok(convert_altair_deltas(deltas))
}
fn compute_altair_inactivity_deltas<E: EthSpec>(
state: &BeaconState<E>,
spec: &ChainSpec,
) -> Result<Deltas, EpochProcessingError> {
let mut deltas = vec![Delta::default(); state.validators().len()];
altair::rewards_and_penalties::get_inactivity_penalty_deltas(&mut deltas, state, spec)?;
Ok(convert_altair_deltas(deltas))
}
fn convert_altair_deltas(deltas: Vec<Delta>) -> Deltas {
let (rewards, penalties) = deltas.into_iter().map(|d| (d.rewards, d.penalties)).unzip();
Deltas { rewards, penalties }
}

View File

@@ -1,12 +1,12 @@
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::decode::{ssz_decode_file_with, ssz_decode_state, yaml_decode_file};
use serde_derive::Deserialize;
use state_processing::{
per_block_processing, per_slot_processing, BlockProcessingError, BlockSignatureStrategy,
};
use types::{BeaconState, EthSpec, RelativeEpoch, SignedBeaconBlock};
use types::{BeaconState, EthSpec, ForkName, RelativeEpoch, SignedBeaconBlock};
#[derive(Debug, Clone, Deserialize)]
pub struct Metadata {
@@ -25,18 +25,21 @@ pub struct SanityBlocks<E: EthSpec> {
}
impl<E: EthSpec> LoadCase for SanityBlocks<E> {
fn load_from_dir(path: &Path) -> Result<Self, Error> {
fn load_from_dir(path: &Path, fork_name: ForkName) -> Result<Self, Error> {
let spec = &testing_spec::<E>(fork_name);
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)
let pre = ssz_decode_state(&path.join("pre.ssz_snappy"), spec)?;
let blocks = (0..metadata.blocks_count)
.map(|i| {
let filename = format!("blocks_{}.ssz", i);
ssz_decode_file(&path.join(filename))
let filename = format!("blocks_{}.ssz_snappy", i);
ssz_decode_file_with(&path.join(filename), |bytes| {
SignedBeaconBlock::from_ssz_bytes(bytes, spec)
})
})
.collect::<Result<_, _>>()?;
let post_file = path.join("post.ssz");
.collect::<Result<Vec<_>, _>>()?;
let post_file = path.join("post.ssz_snappy");
let post = if post_file.is_file() {
Some(ssz_decode_file(&post_file)?)
Some(ssz_decode_state(&post_file, spec)?)
} else {
None
};
@@ -58,12 +61,12 @@ impl<E: EthSpec> Case for SanityBlocks<E> {
.unwrap_or_else(String::new)
}
fn result(&self, _case_index: usize) -> Result<(), Error> {
fn result(&self, _case_index: usize, fork_name: ForkName) -> 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();
let spec = &testing_spec::<E>(fork_name);
// Processing requires the epoch cache.
bulk_state.build_all_caches(spec).unwrap();
@@ -76,8 +79,8 @@ impl<E: EthSpec> Case for SanityBlocks<E> {
.blocks
.iter()
.try_for_each(|signed_block| {
let block = &signed_block.message;
while bulk_state.slot < block.slot {
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();
}
@@ -106,8 +109,8 @@ impl<E: EthSpec> Case for SanityBlocks<E> {
spec,
)?;
if block.state_root == bulk_state.canonical_root()
&& block.state_root == indiv_state.canonical_root()
if block.state_root() == bulk_state.canonical_root()
&& block.state_root() == indiv_state.canonical_root()
{
Ok(())
} else {

View File

@@ -1,10 +1,10 @@
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::decode::{ssz_decode_state, yaml_decode_file};
use serde_derive::Deserialize;
use state_processing::per_slot_processing;
use types::{BeaconState, EthSpec};
use types::{BeaconState, EthSpec, ForkName};
#[derive(Debug, Clone, Default, Deserialize)]
pub struct Metadata {
@@ -22,18 +22,19 @@ pub struct SanitySlots<E: EthSpec> {
}
impl<E: EthSpec> LoadCase for SanitySlots<E> {
fn load_from_dir(path: &Path) -> Result<Self, Error> {
fn load_from_dir(path: &Path, fork_name: ForkName) -> Result<Self, Error> {
let spec = &testing_spec::<E>(fork_name);
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 pre = ssz_decode_state(&path.join("pre.ssz_snappy"), spec)?;
let slots: u64 = yaml_decode_file(&path.join("slots.yaml"))?;
let post_file = path.join("post.ssz");
let post_file = path.join("post.ssz_snappy");
let post = if post_file.is_file() {
Some(ssz_decode_file(&post_file)?)
Some(ssz_decode_state(&post_file, spec)?)
} else {
None
};
@@ -55,12 +56,12 @@ impl<E: EthSpec> Case for SanitySlots<E> {
.unwrap_or_else(String::new)
}
fn result(&self, _case_index: usize) -> Result<(), Error> {
fn result(&self, _case_index: usize, fork_name: ForkName) -> 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();
let spec = &testing_spec::<E>(fork_name);
// Processing requires the epoch cache.
state.build_all_caches(spec).unwrap();

View File

@@ -4,6 +4,7 @@ use crate::decode::yaml_decode_file;
use serde_derive::Deserialize;
use std::marker::PhantomData;
use swap_or_not_shuffle::{compute_shuffled_index, shuffle_list};
use types::ForkName;
#[derive(Debug, Clone, Deserialize)]
pub struct Shuffling<T> {
@@ -15,13 +16,13 @@ pub struct Shuffling<T> {
}
impl<T: EthSpec> LoadCase for Shuffling<T> {
fn load_from_dir(path: &Path) -> Result<Self, Error> {
fn load_from_dir(path: &Path, _fork_name: ForkName) -> 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> {
fn result(&self, _case_index: usize, _fork_name: ForkName) -> Result<(), Error> {
if self.count == 0 {
compare_result::<_, Error>(&Ok(vec![]), &Some(self.mapping.clone()))?;
} else {

View File

@@ -3,15 +3,14 @@
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 crate::decode::{snappy_decode_file, 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};
use types::{BitList, BitVector, FixedVector, ForkName, VariableList};
#[derive(Debug, Clone, Deserialize)]
struct Metadata {
@@ -27,7 +26,7 @@ pub struct SszGeneric {
}
impl LoadCase for SszGeneric {
fn load_from_dir(path: &Path) -> Result<Self, Error> {
fn load_from_dir(path: &Path, _fork_name: ForkName) -> Result<Self, Error> {
let components = path
.components()
.map(|c| c.as_os_str().to_string_lossy().into_owned())
@@ -119,7 +118,7 @@ macro_rules! type_dispatch {
}
impl Case for SszGeneric {
fn result(&self, _case_index: usize) -> 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() {
@@ -195,7 +194,7 @@ impl Case for SszGeneric {
}
}
fn ssz_generic_test<T: SszStaticType>(path: &Path) -> Result<(), Error> {
fn ssz_generic_test<T: SszStaticType + ssz::Decode>(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)?)
@@ -203,7 +202,8 @@ fn ssz_generic_test<T: SszStaticType>(path: &Path) -> Result<(), Error> {
None
};
let serialized = fs::read(&path.join("serialized.ssz")).expect("serialized.ssz exists");
let serialized = snappy_decode_file(&path.join("serialized.ssz_snappy"))
.expect("serialized.ssz_snappy exists");
let value_path = path.join("value.yaml");
let value: Option<T> = if value_path.is_file() {
@@ -215,7 +215,7 @@ fn ssz_generic_test<T: SszStaticType>(path: &Path) -> Result<(), Error> {
// Valid
// TODO: signing root (annoying because of traits)
if let Some(value) = value {
check_serialization(&value, &serialized)?;
check_serialization(&value, &serialized, T::from_ssz_bytes)?;
if let Some(ref meta) = meta {
check_tree_hash(&meta.root, value.tree_hash_root().as_bytes())?;

View File

@@ -1,12 +1,11 @@
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 crate::decode::{snappy_decode_file, yaml_decode_file};
use serde_derive::Deserialize;
use std::fs;
use std::marker::PhantomData;
use types::Hash256;
use ssz::Decode;
use tree_hash::TreeHash;
use types::{BeaconBlock, BeaconState, ForkName, Hash256, SignedBeaconBlock};
#[derive(Debug, Clone, Deserialize)]
struct SszStaticRoots {
@@ -14,6 +13,7 @@ struct SszStaticRoots {
signing_root: Option<String>,
}
/// Runner for types that implement `ssz::Decode`.
#[derive(Debug, Clone)]
pub struct SszStatic<T> {
roots: SszStaticRoots,
@@ -21,24 +21,33 @@ pub struct SszStatic<T> {
value: T,
}
/// Runner for `BeaconState` (with tree hash cache).
#[derive(Debug, Clone)]
pub struct SszStaticTHC<T, C> {
pub struct SszStaticTHC<T> {
roots: SszStaticRoots,
serialized: Vec<u8>,
value: T,
}
/// Runner for types that require a `ChainSpec` to be decoded (`BeaconBlock`, etc).
#[derive(Debug, Clone)]
pub struct SszStaticWithSpec<T> {
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 serialized = snappy_decode_file(&path.join("serialized.ssz_snappy"))
.expect("serialized.ssz_snappy 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> {
fn load_from_dir(path: &Path, _fork_name: ForkName) -> Result<Self, Error> {
load_from_dir(path).map(|(roots, serialized, value)| Self {
roots,
serialized,
@@ -47,25 +56,38 @@ impl<T: SszStaticType> LoadCase for SszStatic<T> {
}
}
impl<T: SszStaticType + CachedTreeHash<C>, C: Debug + Sync> LoadCase for SszStaticTHC<T, C> {
fn load_from_dir(path: &Path) -> Result<Self, Error> {
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,
_phantom: PhantomData,
})
}
}
pub fn check_serialization<T: SszStaticType>(value: &T, serialized: &[u8]) -> Result<(), Error> {
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 {
roots,
serialized,
value,
})
}
}
pub fn check_serialization<T: SszStaticType>(
value: &T,
serialized: &[u8],
deserializer: impl FnOnce(&[u8]) -> Result<T, ssz::DecodeError>,
) -> 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);
let deserialized_result = deserializer(serialized);
compare_result(&deserialized_result, &Some(value.clone()))?;
Ok(())
@@ -79,27 +101,49 @@ pub fn check_tree_hash(expected_str: &str, actual_root: &[u8]) -> Result<(), Err
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)?;
impl<T: SszStaticType + Decode> Case for SszStatic<T> {
fn result(&self, _case_index: usize, _fork_name: ForkName) -> Result<(), Error> {
check_serialization(&self.value, &self.serialized, T::from_ssz_bytes)?;
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)?;
impl<E: EthSpec> Case for SszStaticTHC<BeaconState<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| {
BeaconState::from_ssz_bytes(bytes, spec)
})?;
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();
let mut state = self.value.clone();
state.initialize_tree_hash_cache();
let cached_tree_hash_root = state.update_tree_hash_cache().unwrap();
check_tree_hash(&self.roots.root, cached_tree_hash_root.as_bytes())?;
Ok(())
}
}
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);
check_serialization(&self.value, &self.serialized, |bytes| {
BeaconBlock::from_ssz_bytes(bytes, spec)
})?;
check_tree_hash(&self.roots.root, self.value.tree_hash_root().as_bytes())?;
Ok(())
}
}
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);
check_serialization(&self.value, &self.serialized, |bytes| {
SignedBeaconBlock::from_ssz_bytes(bytes, spec)
})?;
check_tree_hash(&self.roots.root, self.value.tree_hash_root().as_bytes())?;
Ok(())
}
}

View File

@@ -0,0 +1,114 @@
use super::*;
use crate::case_result::compare_beacon_state_results_without_caches;
use crate::decode::{ssz_decode_file_with, ssz_decode_state, yaml_decode_file};
use serde_derive::Deserialize;
use state_processing::{
per_block_processing, state_advance::complete_state_advance, BlockSignatureStrategy,
};
use std::str::FromStr;
use types::{BeaconState, Epoch, ForkName, SignedBeaconBlock};
#[derive(Debug, Clone, Deserialize)]
pub struct Metadata {
pub post_fork: String,
pub fork_epoch: Epoch,
pub fork_block: Option<usize>,
pub blocks_count: usize,
}
#[derive(Debug)]
pub struct TransitionTest<E: EthSpec> {
pub metadata: Metadata,
pub pre: BeaconState<E>,
pub blocks: Vec<SignedBeaconBlock<E>>,
pub post: BeaconState<E>,
pub spec: ChainSpec,
}
impl<E: EthSpec> LoadCase for TransitionTest<E> {
fn load_from_dir(path: &Path, fork_name: ForkName) -> Result<Self, Error> {
let metadata: Metadata = yaml_decode_file(&path.join("meta.yaml"))?;
assert_eq!(ForkName::from_str(&metadata.post_fork).unwrap(), fork_name);
// Make spec with appropriate fork block.
let mut spec = E::default_spec();
match fork_name {
ForkName::Base => panic!("cannot fork to base/phase0"),
ForkName::Altair => {
spec.altair_fork_epoch = Some(metadata.fork_epoch);
}
}
// Load blocks
let blocks = (0..metadata.blocks_count)
.map(|i| {
let filename = format!("blocks_{}.ssz_snappy", i);
ssz_decode_file_with(&path.join(filename), |bytes| {
SignedBeaconBlock::from_ssz_bytes(bytes, &spec)
})
})
.collect::<Result<Vec<_>, _>>()?;
// Decode pre-state.
let pre = ssz_decode_state(&path.join("pre.ssz_snappy"), &spec)?;
// Decode post-state.
let post = ssz_decode_state(&path.join("post.ssz_snappy"), &spec)?;
Ok(Self {
metadata,
pre,
blocks,
post,
spec,
})
}
}
impl<E: EthSpec> Case for TransitionTest<E> {
fn is_enabled_for_fork(fork_name: ForkName) -> bool {
// Upgrades exist targeting all forks except phase0/base.
// Transition tests also need BLS.
cfg!(not(feature = "fake_crypto")) && fork_name != ForkName::Base
}
fn result(&self, _case_index: usize, _fork_name: ForkName) -> Result<(), Error> {
let mut state = self.pre.clone();
let mut expected = Some(self.post.clone());
let spec = &self.spec;
let mut result: Result<_, String> = self
.blocks
.iter()
.try_for_each(|block| {
// Advance to block slot.
complete_state_advance(&mut state, None, block.slot(), spec)
.map_err(|e| format!("Failed to advance: {:?}", e))?;
// Apply block.
per_block_processing(
&mut state,
block,
None,
BlockSignatureStrategy::VerifyBulk,
spec,
)
.map_err(|e| format!("Block processing failed: {:?}", e))?;
let state_root = state.update_tree_hash_cache().unwrap();
if block.state_root() != state_root {
return Err(format!(
"Mismatched state root at slot {}, got: {:?}, expected: {:?}",
block.slot(),
state_root,
block.state_root()
));
}
Ok(())
})
.map(move |()| state);
compare_beacon_state_results_without_caches(&mut result, &mut expected)
}
}

View File

@@ -1,12 +1,42 @@
use super::*;
use std::fs;
use fs2::FileExt;
use snap::raw::Decoder;
use std::fs::{self};
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use types::{BeaconState, EthSpec};
/// See `log_file_access` for details.
const ACCESSED_FILE_LOG_FILENAME: &str = ".accessed_file_log.txt";
/// Writes `path` to a file that contains a log of all files accessed during testing.
///
/// That log file might later be used to ensure that all spec tests were accessed and none were
/// accidentally missed.
pub fn log_file_access<P: AsRef<Path>>(file_accessed: P) {
let passed_test_list_path =
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(ACCESSED_FILE_LOG_FILENAME);
let mut file = fs::OpenOptions::new()
.append(true)
.create(true)
.open(passed_test_list_path)
.expect("should open file");
file.lock_exclusive().expect("unable to lock file");
writeln!(&mut file, "{:?}", file_accessed.as_ref()).expect("should write to file");
file.unlock().expect("unable to unlock file");
}
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> {
log_file_access(path);
fs::read_to_string(path)
.map_err(|e| {
Error::FailedToParseTest(format!("Unable to load {}: {:?}", path.display(), e))
@@ -14,26 +44,56 @@ pub fn yaml_decode_file<T: serde::de::DeserializeOwned>(path: &Path) -> Result<T
.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| {
match e {
// NOTE: this is a bit hacky, but seemingly better than the alternatives
ssz::DecodeError::BytesInvalid(message)
if message.contains("Blst") || message.contains("Milagro") =>
{
Error::InvalidBLSInput(message)
}
e => Error::FailedToParseTest(format!(
"Unable to parse SSZ at {}: {:?}",
path.display(),
e
)),
}
})
})
/// Decode a Snappy encoded file.
///
/// Files in the EF tests are unframed, so we need to use `snap::raw::Decoder`.
pub fn snappy_decode_file(path: &Path) -> Result<Vec<u8>, Error> {
log_file_access(path);
let bytes = fs::read(path).map_err(|e| {
Error::FailedToParseTest(format!("Unable to load {}: {:?}", path.display(), e))
})?;
let mut decoder = Decoder::new();
decoder.decompress_vec(&bytes).map_err(|e| {
Error::FailedToParseTest(format!(
"Error decoding snappy encoding for {}: {:?}",
path.display(),
e
))
})
}
pub fn ssz_decode_file_with<T, F>(path: &Path, f: F) -> Result<T, Error>
where
F: FnOnce(&[u8]) -> Result<T, ssz::DecodeError>,
{
log_file_access(path);
let bytes = snappy_decode_file(path)?;
f(&bytes).map_err(|e| {
match e {
// NOTE: this is a bit hacky, but seemingly better than the alternatives
ssz::DecodeError::BytesInvalid(message)
if message.contains("Blst") || message.contains("Milagro") =>
{
Error::InvalidBLSInput(message)
}
e => Error::FailedToParseTest(format!(
"Unable to parse SSZ at {}: {:?}",
path.display(),
e
)),
}
})
}
pub fn ssz_decode_file<T: ssz::Decode>(path: &Path) -> Result<T, Error> {
log_file_access(path);
ssz_decode_file_with(path, T::from_ssz_bytes)
}
pub fn ssz_decode_state<E: EthSpec>(
path: &Path,
spec: &ChainSpec,
) -> Result<BeaconState<E>, Error> {
log_file_access(path);
ssz_decode_file_with(path, |bytes| BeaconState::from_ssz_bytes(bytes, spec))
}

View File

@@ -1,12 +1,11 @@
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 derivative::Derivative;
use std::fs;
use std::marker::PhantomData;
use std::path::PathBuf;
use types::EthSpec;
use types::{BeaconState, EthSpec, ForkName};
pub trait Handler {
type Case: Case + LoadCase;
@@ -15,22 +14,35 @@ pub trait Handler {
"general"
}
fn fork_name() -> &'static str {
"phase0"
}
fn runner_name() -> &'static str;
fn handler_name() -> String;
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 run_for_fork(&self, fork_name: ForkName) {
let fork_name_str = match fork_name {
ForkName::Base => "phase0",
ForkName::Altair => "altair",
};
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(fork_name_str)
.join(Self::runner_name())
.join(Self::handler_name());
.join(self.handler_name());
// Iterate through test suites
let test_cases = fs::read_dir(&handler_path)
@@ -44,30 +56,41 @@ pub trait Handler {
.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");
let case = Self::Case::load_from_dir(&path, fork_name).expect("test should load");
(path, case)
})
.collect();
let results = Cases { test_cases }.test_results();
let results = Cases { test_cases }.test_results(fork_name);
let name = format!("{}/{}", Self::runner_name(), Self::handler_name());
let name = format!(
"{}/{}/{}",
fork_name_str,
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) => {
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub struct $runner_name;
impl Handler for $runner_name {
type Case = cases::$case_name;
fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool {
fork_name == ForkName::Base
}
fn runner_name() -> &'static str {
"bls"
}
fn handler_name() -> String {
fn handler_name(&self) -> String {
$handler_name.into()
}
}
@@ -89,14 +112,47 @@ bls_handler!(
);
/// Handler for SSZ types.
pub struct SszStaticHandler<T, E>(PhantomData<(T, E)>);
pub struct SszStaticHandler<T, E> {
supported_forks: Vec<ForkName>,
_phantom: PhantomData<(T, E)>,
}
impl<T, E> Default for SszStaticHandler<T, E> {
fn default() -> Self {
Self::for_forks(ForkName::list_all())
}
}
impl<T, E> SszStaticHandler<T, E> {
pub fn for_forks(supported_forks: Vec<ForkName>) -> 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])
}
}
/// Handler for SSZ types that implement `CachedTreeHash`.
pub struct SszStaticTHCHandler<T, C, E>(PhantomData<(T, C, E)>);
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub struct SszStaticTHCHandler<T, E>(PhantomData<(T, E)>);
/// Handler for SSZ types that don't implement `ssz::Decode`.
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub struct SszStaticWithSpecHandler<T, E>(PhantomData<(T, E)>);
impl<T, E> Handler for SszStaticHandler<T, E>
where
T: cases::SszStaticType + TypeName,
T: cases::SszStaticType + ssz::Decode + TypeName,
E: TypeName,
{
type Case = cases::SszStatic<T>;
@@ -109,18 +165,20 @@ where
"ssz_static"
}
fn handler_name() -> String {
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<T, C, E> Handler for SszStaticTHCHandler<T, C, E>
impl<E> Handler for SszStaticTHCHandler<BeaconState<E>, E>
where
T: cases::SszStaticType + CachedTreeHash<C> + TypeName,
C: Debug + Sync,
E: TypeName,
E: EthSpec + TypeName,
{
type Case = cases::SszStaticTHC<T, C>;
type Case = cases::SszStaticTHC<BeaconState<E>>;
fn config_name() -> &'static str {
E::name()
@@ -130,11 +188,34 @@ where
"ssz_static"
}
fn handler_name() -> String {
fn handler_name(&self) -> String {
BeaconState::<E>::name().into()
}
}
impl<T, E> Handler for SszStaticWithSpecHandler<T, E>
where
T: TypeName,
E: EthSpec + TypeName,
cases::SszStaticWithSpec<T>: Case + LoadCase,
{
type Case = cases::SszStaticWithSpec<T>;
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<E>(PhantomData<E>);
impl<E: EthSpec + TypeName> Handler for ShufflingHandler<E> {
@@ -148,11 +229,17 @@ impl<E: EthSpec + TypeName> Handler for ShufflingHandler<E> {
"shuffling"
}
fn handler_name() -> String {
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<E>(PhantomData<E>);
impl<E: EthSpec + TypeName> Handler for SanityBlocksHandler<E> {
@@ -166,11 +253,19 @@ impl<E: EthSpec + TypeName> Handler for SanityBlocksHandler<E> {
"sanity"
}
fn handler_name() -> String {
fn handler_name(&self) -> String {
"blocks".into()
}
fn is_enabled_for_fork(&self, _fork_name: ForkName) -> bool {
// FIXME(altair): v1.1.0-alpha.3 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<E>(PhantomData<E>);
impl<E: EthSpec + TypeName> Handler for SanitySlotsHandler<E> {
@@ -184,11 +279,13 @@ impl<E: EthSpec + TypeName> Handler for SanitySlotsHandler<E> {
"sanity"
}
fn handler_name() -> String {
fn handler_name(&self) -> String {
"slots".into()
}
}
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub struct EpochProcessingHandler<E, T>(PhantomData<(E, T)>);
impl<E: EthSpec + TypeName, T: EpochTransition<E>> Handler for EpochProcessingHandler<E, T> {
@@ -202,11 +299,83 @@ impl<E: EthSpec + TypeName, T: EpochTransition<E>> Handler for EpochProcessingHa
"epoch_processing"
}
fn handler_name() -> String {
fn handler_name(&self) -> String {
T::name().into()
}
}
pub struct RewardsHandler<E: EthSpec> {
handler_name: &'static str,
_phantom: PhantomData<E>,
}
impl<E: EthSpec> RewardsHandler<E> {
pub fn new(handler_name: &'static str) -> Self {
Self {
handler_name,
_phantom: PhantomData,
}
}
}
impl<E: EthSpec + TypeName> Handler for RewardsHandler<E> {
type Case = cases::RewardsTest<E>;
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<E>(PhantomData<E>);
impl<E: EthSpec + TypeName> Handler for ForkHandler<E> {
type Case = cases::ForkTest<E>;
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<E>(PhantomData<E>);
impl<E: EthSpec + TypeName> Handler for TransitionHandler<E> {
type Case = cases::TransitionTest<E>;
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<E>(PhantomData<E>);
impl<E: EthSpec + TypeName> Handler for FinalityHandler<E> {
@@ -221,11 +390,13 @@ impl<E: EthSpec + TypeName> Handler for FinalityHandler<E> {
"finality"
}
fn handler_name() -> String {
fn handler_name(&self) -> String {
"finality".into()
}
}
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub struct GenesisValidityHandler<E>(PhantomData<E>);
impl<E: EthSpec + TypeName> Handler for GenesisValidityHandler<E> {
@@ -239,11 +410,13 @@ impl<E: EthSpec + TypeName> Handler for GenesisValidityHandler<E> {
"genesis"
}
fn handler_name() -> String {
fn handler_name(&self) -> String {
"validity".into()
}
}
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub struct GenesisInitializationHandler<E>(PhantomData<E>);
impl<E: EthSpec + TypeName> Handler for GenesisInitializationHandler<E> {
@@ -257,11 +430,13 @@ impl<E: EthSpec + TypeName> Handler for GenesisInitializationHandler<E> {
"genesis"
}
fn handler_name() -> String {
fn handler_name(&self) -> String {
"initialization".into()
}
}
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub struct OperationsHandler<E, O>(PhantomData<(E, O)>);
impl<E: EthSpec + TypeName, O: Operation<E>> Handler for OperationsHandler<E, O> {
@@ -275,11 +450,13 @@ impl<E: EthSpec + TypeName, O: Operation<E>> Handler for OperationsHandler<E, O>
"operations"
}
fn handler_name() -> String {
fn handler_name(&self) -> String {
O::handler_name()
}
}
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub struct SszGenericHandler<H>(PhantomData<H>);
impl<H: TypeName> Handler for SszGenericHandler<H> {
@@ -293,7 +470,12 @@ impl<H: TypeName> Handler for SszGenericHandler<H> {
"ssz_generic"
}
fn handler_name() -> String {
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()
}
}

View File

@@ -1,13 +1,16 @@
use types::EthSpec;
pub use case_result::CaseResult;
pub use cases::Case;
pub use cases::{
FinalUpdates, JustificationAndFinalization, RegistryUpdates, RewardsAndPenalties, Slashings,
EffectiveBalanceUpdates, Eth1DataReset, HistoricalRootsUpdate, InactivityUpdates,
JustificationAndFinalization, ParticipationFlagUpdates, ParticipationRecordUpdates,
RandaoMixesReset, RegistryUpdates, RewardsAndPenalties, Slashings, SlashingsReset,
SyncCommitteeUpdates,
};
pub use decode::log_file_access;
pub use error::Error;
pub use handler::*;
pub use type_name::TypeName;
use types::{ChainSpec, EthSpec, ForkName};
mod bls_setting;
mod case_result;
@@ -17,3 +20,7 @@ mod error;
mod handler;
mod results;
mod type_name;
pub fn testing_spec<E: EthSpec>(fork_name: ForkName) -> ChainSpec {
fork_name.make_genesis_spec(E::default_spec())
}

View File

@@ -41,9 +41,15 @@ type_name_generic!(Attestation);
type_name!(AttestationData);
type_name_generic!(AttesterSlashing);
type_name_generic!(BeaconBlock);
type_name_generic!(BeaconBlockBase, "BeaconBlock");
type_name_generic!(BeaconBlockAltair, "BeaconBlock");
type_name_generic!(BeaconBlockBody);
type_name_generic!(BeaconBlockBodyBase, "BeaconBlockBody");
type_name_generic!(BeaconBlockBodyAltair, "BeaconBlockBody");
type_name!(BeaconBlockHeader);
type_name_generic!(BeaconState);
type_name_generic!(BeaconStateBase, "BeaconState");
type_name_generic!(BeaconStateAltair, "BeaconState");
type_name!(Checkpoint);
type_name!(Deposit);
type_name!(DepositData);
@@ -60,5 +66,7 @@ type_name_generic!(SignedBeaconBlock);
type_name!(SignedBeaconBlockHeader);
type_name!(SignedVoluntaryExit);
type_name!(SigningData);
type_name_generic!(SyncAggregate);
type_name_generic!(SyncCommittee);
type_name!(Validator);
type_name!(VoluntaryExit);

View File

@@ -1,40 +1,8 @@
#![cfg(feature = "ef_tests")]
use ef_tests::*;
use std::collections::HashMap;
use std::path::PathBuf;
use types::*;
// Check that the config from the Eth2.0 spec tests matches our minimal/mainnet config.
fn config_test<E: EthSpec + TypeName>() {
let config_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("eth2.0-spec-tests")
.join("tests")
.join(E::name())
.join("config")
.join("phase0.yaml");
let yaml_config = YamlConfig::from_file(&config_path).expect("config file loads OK");
let spec = E::default_spec();
let yaml_from_spec = YamlConfig::from_spec::<E>(&spec);
assert_eq!(yaml_config.apply_to_chain_spec::<E>(&spec), Some(spec));
assert_eq!(yaml_from_spec, yaml_config);
assert_eq!(
yaml_config.extra_fields,
HashMap::new(),
"not all config fields read"
);
}
#[test]
fn mainnet_config_ok() {
config_test::<MainnetEthSpec>();
}
#[test]
fn minimal_config_ok() {
config_test::<MinimalEthSpec>();
}
// Check that the hand-computed multiplications on EthSpec are correctly computed.
// This test lives here because one is most likely to muck these up during a spec update.
fn check_typenum_values<E: EthSpec>() {
@@ -56,118 +24,141 @@ fn derived_typenum_values() {
#[test]
fn shuffling() {
ShufflingHandler::<MinimalEthSpec>::run();
ShufflingHandler::<MainnetEthSpec>::run();
ShufflingHandler::<MinimalEthSpec>::default().run();
ShufflingHandler::<MainnetEthSpec>::default().run();
}
#[test]
fn operations_deposit() {
OperationsHandler::<MinimalEthSpec, Deposit>::run();
OperationsHandler::<MainnetEthSpec, Deposit>::run();
OperationsHandler::<MinimalEthSpec, Deposit>::default().run();
OperationsHandler::<MainnetEthSpec, Deposit>::default().run();
}
#[test]
fn operations_exit() {
OperationsHandler::<MinimalEthSpec, SignedVoluntaryExit>::run();
OperationsHandler::<MainnetEthSpec, SignedVoluntaryExit>::run();
OperationsHandler::<MinimalEthSpec, SignedVoluntaryExit>::default().run();
OperationsHandler::<MainnetEthSpec, SignedVoluntaryExit>::default().run();
}
#[test]
fn operations_proposer_slashing() {
OperationsHandler::<MinimalEthSpec, ProposerSlashing>::run();
OperationsHandler::<MainnetEthSpec, ProposerSlashing>::run();
OperationsHandler::<MinimalEthSpec, ProposerSlashing>::default().run();
OperationsHandler::<MainnetEthSpec, ProposerSlashing>::default().run();
}
#[test]
fn operations_attester_slashing() {
OperationsHandler::<MinimalEthSpec, AttesterSlashing<_>>::run();
OperationsHandler::<MainnetEthSpec, AttesterSlashing<_>>::run();
OperationsHandler::<MinimalEthSpec, AttesterSlashing<_>>::default().run();
OperationsHandler::<MainnetEthSpec, AttesterSlashing<_>>::default().run();
}
#[test]
fn operations_attestation() {
OperationsHandler::<MinimalEthSpec, Attestation<_>>::run();
OperationsHandler::<MainnetEthSpec, Attestation<_>>::run();
OperationsHandler::<MinimalEthSpec, Attestation<_>>::default().run();
OperationsHandler::<MainnetEthSpec, Attestation<_>>::default().run();
}
#[test]
fn operations_block_header() {
OperationsHandler::<MinimalEthSpec, BeaconBlock<_>>::run();
OperationsHandler::<MainnetEthSpec, BeaconBlock<_>>::run();
OperationsHandler::<MinimalEthSpec, BeaconBlock<_>>::default().run();
OperationsHandler::<MainnetEthSpec, BeaconBlock<_>>::default().run();
}
#[test]
fn operations_sync_aggregate() {
OperationsHandler::<MinimalEthSpec, SyncAggregate<_>>::default().run();
OperationsHandler::<MainnetEthSpec, SyncAggregate<_>>::default().run();
}
#[test]
fn sanity_blocks() {
SanityBlocksHandler::<MinimalEthSpec>::run();
SanityBlocksHandler::<MainnetEthSpec>::run();
SanityBlocksHandler::<MinimalEthSpec>::default().run();
SanityBlocksHandler::<MainnetEthSpec>::default().run();
}
#[test]
fn sanity_slots() {
SanitySlotsHandler::<MinimalEthSpec>::run();
SanitySlotsHandler::<MainnetEthSpec>::run();
SanitySlotsHandler::<MinimalEthSpec>::default().run();
SanitySlotsHandler::<MainnetEthSpec>::default().run();
}
#[test]
#[cfg(not(feature = "fake_crypto"))]
fn bls_aggregate() {
BlsAggregateSigsHandler::run();
BlsAggregateSigsHandler::default().run();
}
#[test]
#[cfg(not(feature = "fake_crypto"))]
fn bls_sign() {
BlsSignMsgHandler::run();
BlsSignMsgHandler::default().run();
}
#[test]
#[cfg(not(feature = "fake_crypto"))]
fn bls_verify() {
BlsVerifyMsgHandler::run();
BlsVerifyMsgHandler::default().run();
}
#[test]
#[cfg(not(feature = "fake_crypto"))]
fn bls_aggregate_verify() {
BlsAggregateVerifyHandler::run();
BlsAggregateVerifyHandler::default().run();
}
#[test]
#[cfg(not(feature = "fake_crypto"))]
fn bls_fast_aggregate_verify() {
BlsFastAggregateVerifyHandler::run();
BlsFastAggregateVerifyHandler::default().run();
}
/// As for `ssz_static_test_no_run` (below), but also executes the function as a test.
#[cfg(feature = "fake_crypto")]
macro_rules! ssz_static_test {
// Non-tree hash caching
($test_name:ident, $typ:ident$(<$generics:tt>)?) => {
ssz_static_test!($test_name, SszStaticHandler, $typ$(<$generics>)?);
($($args:tt)*) => {
ssz_static_test_no_run!(#[test] $($args)*);
};
}
/// Generate a function to run the SSZ static tests for a type.
///
/// Quite complex in order to support an optional #[test] attrib, generics, and the two EthSpecs.
#[cfg(feature = "fake_crypto")]
macro_rules! ssz_static_test_no_run {
// Top-level
($(#[$test:meta])? $test_name:ident, $typ:ident$(<$generics:tt>)?) => {
ssz_static_test_no_run!($(#[$test])? $test_name, SszStaticHandler, $typ$(<$generics>)?);
};
// Generic
($test_name:ident, $handler:ident, $typ:ident<_>) => {
ssz_static_test!(
$test_name, $handler, {
($(#[$test:meta])? $test_name:ident, $handler:ident, $typ:ident<_>) => {
ssz_static_test_no_run!(
$(#[$test])?
$test_name,
$handler,
{
($typ<MinimalEthSpec>, MinimalEthSpec),
($typ<MainnetEthSpec>, MainnetEthSpec)
}
);
};
// Non-generic
($test_name:ident, $handler:ident, $typ:ident) => {
ssz_static_test!(
$test_name, $handler, {
($(#[$test:meta])? $test_name:ident, $handler:ident, $typ:ident) => {
ssz_static_test_no_run!(
$(#[$test])?
$test_name,
$handler,
{
($typ, MinimalEthSpec),
($typ, MainnetEthSpec)
}
);
};
// Base case
($test_name:ident, $handler:ident, { $(($($typ:ty),+)),+ }) => {
#[test]
($(#[$test:meta])? $test_name:ident, $handler:ident, { $(($($typ:ty),+)),+ }) => {
$(#[$test])?
fn $test_name() {
$(
$handler::<$($typ),+>::run();
$handler::<$($typ),+>::default().run();
)+
}
};
@@ -175,101 +166,190 @@ macro_rules! ssz_static_test {
#[cfg(feature = "fake_crypto")]
mod ssz_static {
use ef_tests::{Handler, SszStaticHandler, SszStaticTHCHandler};
use ef_tests::{Handler, SszStaticHandler, SszStaticTHCHandler, SszStaticWithSpecHandler};
use types::*;
ssz_static_test!(aggregate_and_proof, AggregateAndProof<_>);
ssz_static_test!(attestation, Attestation<_>);
ssz_static_test!(attestation_data, AttestationData);
ssz_static_test!(attester_slashing, AttesterSlashing<_>);
ssz_static_test!(beacon_block, BeaconBlock<_>);
ssz_static_test!(beacon_block_body, BeaconBlockBody<_>);
ssz_static_test!(beacon_block, SszStaticWithSpecHandler, BeaconBlock<_>);
ssz_static_test!(beacon_block_header, BeaconBlockHeader);
ssz_static_test!(
beacon_state,
SszStaticTHCHandler, {
(BeaconState<MinimalEthSpec>, BeaconTreeHashCache<_>, MinimalEthSpec),
(BeaconState<MainnetEthSpec>, BeaconTreeHashCache<_>, MainnetEthSpec)
}
);
ssz_static_test!(beacon_state, SszStaticTHCHandler, BeaconState<_>);
ssz_static_test!(checkpoint, Checkpoint);
// FIXME(altair): add ContributionAndProof
ssz_static_test!(deposit, Deposit);
ssz_static_test!(deposit_data, DepositData);
ssz_static_test!(deposit_message, DepositMessage);
// FIXME(sproul): move Eth1Block to consensus/types
//
// Tracked at: https://github.com/sigp/lighthouse/issues/1835
//
// ssz_static_test!(eth1_block, Eth1Block);
// NOTE: Eth1Block intentionally omitted, see: https://github.com/sigp/lighthouse/issues/1835
ssz_static_test!(eth1_data, Eth1Data);
ssz_static_test!(fork, Fork);
ssz_static_test!(fork_data, ForkData);
ssz_static_test!(historical_batch, HistoricalBatch<_>);
ssz_static_test!(indexed_attestation, IndexedAttestation<_>);
// NOTE: LightClient* intentionally omitted
ssz_static_test!(pending_attestation, PendingAttestation<_>);
ssz_static_test!(proposer_slashing, ProposerSlashing);
ssz_static_test!(signed_aggregate_and_proof, SignedAggregateAndProof<_>);
ssz_static_test!(signed_beacon_block, SignedBeaconBlock<_>);
ssz_static_test!(
signed_beacon_block,
SszStaticWithSpecHandler,
SignedBeaconBlock<_>
);
ssz_static_test!(signed_beacon_block_header, SignedBeaconBlockHeader);
// FIXME(altair): add SignedContributionAndProof
ssz_static_test!(signed_voluntary_exit, SignedVoluntaryExit);
ssz_static_test!(signing_data, SigningData);
// FIXME(altair): add SyncCommitteeContribution/Signature/SigningData
ssz_static_test!(validator, Validator);
ssz_static_test!(voluntary_exit, VoluntaryExit);
// BeaconBlockBody has no internal indicator of which fork it is for, so we test it separately.
#[test]
fn beacon_block_body() {
SszStaticHandler::<BeaconBlockBodyBase<MinimalEthSpec>, MinimalEthSpec>::base_only().run();
SszStaticHandler::<BeaconBlockBodyBase<MainnetEthSpec>, MainnetEthSpec>::base_only().run();
SszStaticHandler::<BeaconBlockBodyAltair<MinimalEthSpec>, MinimalEthSpec>::altair_only()
.run();
SszStaticHandler::<BeaconBlockBodyAltair<MainnetEthSpec>, MainnetEthSpec>::altair_only()
.run();
}
// Altair-only
#[test]
fn sync_aggregate() {
SszStaticHandler::<SyncAggregate<MinimalEthSpec>, MinimalEthSpec>::altair_only().run();
SszStaticHandler::<SyncAggregate<MainnetEthSpec>, MainnetEthSpec>::altair_only().run();
}
#[test]
fn sync_committee() {
SszStaticHandler::<SyncCommittee<MinimalEthSpec>, MinimalEthSpec>::altair_only().run();
SszStaticHandler::<SyncCommittee<MainnetEthSpec>, MainnetEthSpec>::altair_only().run();
}
}
#[test]
fn ssz_generic() {
SszGenericHandler::<BasicVector>::run();
SszGenericHandler::<Bitlist>::run();
SszGenericHandler::<Bitvector>::run();
SszGenericHandler::<Boolean>::run();
SszGenericHandler::<Uints>::run();
SszGenericHandler::<Containers>::run();
SszGenericHandler::<BasicVector>::default().run();
SszGenericHandler::<Bitlist>::default().run();
SszGenericHandler::<Bitvector>::default().run();
SszGenericHandler::<Boolean>::default().run();
SszGenericHandler::<Uints>::default().run();
SszGenericHandler::<Containers>::default().run();
}
#[test]
fn epoch_processing_justification_and_finalization() {
EpochProcessingHandler::<MinimalEthSpec, JustificationAndFinalization>::run();
EpochProcessingHandler::<MainnetEthSpec, JustificationAndFinalization>::run();
EpochProcessingHandler::<MinimalEthSpec, JustificationAndFinalization>::default().run();
EpochProcessingHandler::<MainnetEthSpec, JustificationAndFinalization>::default().run();
}
#[test]
fn epoch_processing_rewards_and_penalties() {
EpochProcessingHandler::<MinimalEthSpec, RewardsAndPenalties>::run();
EpochProcessingHandler::<MainnetEthSpec, RewardsAndPenalties>::run();
EpochProcessingHandler::<MinimalEthSpec, RewardsAndPenalties>::default().run();
EpochProcessingHandler::<MainnetEthSpec, RewardsAndPenalties>::default().run();
}
#[test]
fn epoch_processing_registry_updates() {
EpochProcessingHandler::<MinimalEthSpec, RegistryUpdates>::run();
EpochProcessingHandler::<MainnetEthSpec, RegistryUpdates>::run();
EpochProcessingHandler::<MinimalEthSpec, RegistryUpdates>::default().run();
EpochProcessingHandler::<MainnetEthSpec, RegistryUpdates>::default().run();
}
#[test]
fn epoch_processing_slashings() {
EpochProcessingHandler::<MinimalEthSpec, Slashings>::run();
EpochProcessingHandler::<MainnetEthSpec, Slashings>::run();
EpochProcessingHandler::<MinimalEthSpec, Slashings>::default().run();
EpochProcessingHandler::<MainnetEthSpec, Slashings>::default().run();
}
#[test]
fn epoch_processing_final_updates() {
EpochProcessingHandler::<MinimalEthSpec, FinalUpdates>::run();
EpochProcessingHandler::<MainnetEthSpec, FinalUpdates>::run();
fn epoch_processing_eth1_data_reset() {
EpochProcessingHandler::<MinimalEthSpec, Eth1DataReset>::default().run();
EpochProcessingHandler::<MainnetEthSpec, Eth1DataReset>::default().run();
}
#[test]
fn epoch_processing_effective_balance_updates() {
EpochProcessingHandler::<MinimalEthSpec, EffectiveBalanceUpdates>::default().run();
EpochProcessingHandler::<MainnetEthSpec, EffectiveBalanceUpdates>::default().run();
}
#[test]
fn epoch_processing_slashings_reset() {
EpochProcessingHandler::<MinimalEthSpec, SlashingsReset>::default().run();
EpochProcessingHandler::<MainnetEthSpec, SlashingsReset>::default().run();
}
#[test]
fn epoch_processing_randao_mixes_reset() {
EpochProcessingHandler::<MinimalEthSpec, RandaoMixesReset>::default().run();
EpochProcessingHandler::<MainnetEthSpec, RandaoMixesReset>::default().run();
}
#[test]
fn epoch_processing_historical_roots_update() {
EpochProcessingHandler::<MinimalEthSpec, HistoricalRootsUpdate>::default().run();
EpochProcessingHandler::<MainnetEthSpec, HistoricalRootsUpdate>::default().run();
}
#[test]
fn epoch_processing_participation_record_updates() {
EpochProcessingHandler::<MinimalEthSpec, ParticipationRecordUpdates>::default().run();
EpochProcessingHandler::<MainnetEthSpec, ParticipationRecordUpdates>::default().run();
}
#[test]
fn epoch_processing_sync_committee_updates() {
EpochProcessingHandler::<MinimalEthSpec, SyncCommitteeUpdates>::default().run();
EpochProcessingHandler::<MainnetEthSpec, SyncCommitteeUpdates>::default().run();
}
#[test]
fn epoch_processing_inactivity_updates() {
EpochProcessingHandler::<MinimalEthSpec, InactivityUpdates>::default().run();
EpochProcessingHandler::<MainnetEthSpec, InactivityUpdates>::default().run();
}
#[test]
fn epoch_processing_participation_flag_updates() {
EpochProcessingHandler::<MinimalEthSpec, ParticipationFlagUpdates>::default().run();
EpochProcessingHandler::<MainnetEthSpec, ParticipationFlagUpdates>::default().run();
}
#[test]
fn fork_upgrade() {
ForkHandler::<MinimalEthSpec>::default().run();
ForkHandler::<MainnetEthSpec>::default().run();
}
#[test]
fn transition() {
TransitionHandler::<MinimalEthSpec>::default().run();
TransitionHandler::<MainnetEthSpec>::default().run();
}
#[test]
fn finality() {
FinalityHandler::<MinimalEthSpec>::run();
FinalityHandler::<MainnetEthSpec>::run();
FinalityHandler::<MinimalEthSpec>::default().run();
FinalityHandler::<MainnetEthSpec>::default().run();
}
#[test]
fn genesis_initialization() {
GenesisInitializationHandler::<MinimalEthSpec>::run();
GenesisInitializationHandler::<MinimalEthSpec>::default().run();
}
#[test]
fn genesis_validity() {
GenesisValidityHandler::<MinimalEthSpec>::run();
GenesisValidityHandler::<MinimalEthSpec>::default().run();
// Note: there are no genesis validity tests for mainnet
}
#[test]
fn rewards() {
for handler in &["basic", "leak", "random"] {
RewardsHandler::<MinimalEthSpec>::new(handler).run();
RewardsHandler::<MainnetEthSpec>::new(handler).run();
}
}

View File

@@ -42,7 +42,7 @@ impl LocalSignerTestData<BeaconBlock<E>> {
&self.spec,
);
signed_block.signature.to_string()
signed_block.signature().to_string()
}
}

View File

@@ -239,31 +239,39 @@ pub fn get_block<E: EthSpec>(seed: u64) -> BeaconBlock<E> {
let mut block: BeaconBlock<E> = BeaconBlock::empty(spec);
for _ in 0..E::MaxProposerSlashings::to_usize() {
block
.body
.proposer_slashings
.body_mut()
.proposer_slashings_mut()
.push(proposer_slashing.clone())
.unwrap();
}
for _ in 0..E::MaxDeposits::to_usize() {
block.body.deposits.push(deposit.clone()).unwrap();
block
.body_mut()
.deposits_mut()
.push(deposit.clone())
.unwrap();
}
for _ in 0..E::MaxVoluntaryExits::to_usize() {
block
.body
.voluntary_exits
.body_mut()
.voluntary_exits_mut()
.push(signed_voluntary_exit.clone())
.unwrap();
}
for _ in 0..E::MaxAttesterSlashings::to_usize() {
block
.body
.attester_slashings
.body_mut()
.attester_slashings_mut()
.push(attester_slashing.clone())
.unwrap();
}
for _ in 0..E::MaxAttestations::to_usize() {
block.body.attestations.push(attestation.clone()).unwrap();
block
.body_mut()
.attestations_mut()
.push(attestation.clone())
.unwrap();
}
block
}

View File

@@ -99,7 +99,7 @@ async fn verify_validator_count<E: EthSpec>(
.await
.map(|body| body.unwrap().data)
.map_err(|e| format!("Get state root via http failed: {:?}", e))?
.validators
.validators()
.len();
validator_counts.push(vc);
}

View File

@@ -10,3 +10,5 @@ edition = "2018"
state_processing = { path = "../../consensus/state_processing" }
types = { path = "../../consensus/types" }
eth2_ssz = "0.1.2"
beacon_chain = { path = "../../beacon_node/beacon_chain" }
lazy_static = "1.4.0"

View File

@@ -1,7 +1,8 @@
use super::*;
use beacon_chain::test_utils::{BeaconChainHarness, EphemeralHarnessType};
use state_processing::{
per_block_processing, per_block_processing::errors::ExitInvalid,
test_utils::BlockProcessingBuilder, BlockProcessingError, BlockSignatureStrategy,
per_block_processing, per_block_processing::errors::ExitInvalid, BlockProcessingError,
BlockSignatureStrategy,
};
use types::{BeaconBlock, BeaconState, Epoch, EthSpec, SignedBeaconBlock};
@@ -14,8 +15,10 @@ struct ExitTest {
validator_index: u64,
exit_epoch: Epoch,
state_epoch: Epoch,
block_modifier: Box<dyn FnOnce(&mut BeaconBlock<E>)>,
builder_modifier: Box<dyn FnOnce(BlockProcessingBuilder<E>) -> BlockProcessingBuilder<E>>,
state_modifier: Box<dyn FnOnce(&mut BeaconState<E>)>,
#[allow(clippy::type_complexity)]
block_modifier:
Box<dyn FnOnce(&BeaconChainHarness<EphemeralHarnessType<E>>, &mut BeaconBlock<E>)>,
#[allow(dead_code)]
expected: Result<(), BlockProcessingError>,
}
@@ -26,8 +29,8 @@ impl Default for ExitTest {
validator_index: VALIDATOR_INDEX,
exit_epoch: STATE_EPOCH,
state_epoch: STATE_EPOCH,
block_modifier: Box::new(|_| ()),
builder_modifier: Box::new(|x| x),
state_modifier: Box::new(|_| ()),
block_modifier: Box::new(|_, _| ()),
expected: Ok(()),
}
}
@@ -35,14 +38,23 @@ impl Default for ExitTest {
impl ExitTest {
fn block_and_pre_state(self) -> (SignedBeaconBlock<E>, BeaconState<E>) {
let spec = &E::default_spec();
let harness = get_harness::<E>(
self.state_epoch.start_slot(E::slots_per_epoch()),
VALIDATOR_COUNT,
);
let mut state = harness.get_current_state();
(self.state_modifier)(&mut state);
(self.builder_modifier)(
get_builder(spec, self.state_epoch.as_u64(), VALIDATOR_COUNT)
.insert_exit(self.validator_index, self.exit_epoch)
.modify(self.block_modifier),
)
.build(None, None)
let block_modifier = self.block_modifier;
let validator_index = self.validator_index;
let exit_epoch = self.exit_epoch;
let (signed_block, state) =
harness.make_block_with_modifier(state.clone(), state.slot() + 1, |block| {
harness.add_voluntary_exit(block, validator_index, exit_epoch);
block_modifier(&harness, block);
});
(signed_block, state)
}
fn process(
@@ -58,7 +70,7 @@ impl ExitTest {
)
}
#[cfg(test)]
#[cfg(all(test, not(debug_assertions)))]
fn run(self) -> BeaconState<E> {
let spec = &E::default_spec();
let expected = self.expected.clone();
@@ -95,23 +107,22 @@ vectors_and_tests!(
// Ensures we can process a valid exit,
valid_single_exit,
ExitTest::default(),
// Tests three exists in the same block.
// Tests three exits in the same block.
valid_three_exits,
ExitTest {
builder_modifier: Box::new(|builder| {
builder
.insert_exit(1, STATE_EPOCH)
.insert_exit(2, STATE_EPOCH)
block_modifier: Box::new(|harness, block| {
harness.add_voluntary_exit(block, 1, STATE_EPOCH);
harness.add_voluntary_exit(block, 2, STATE_EPOCH);
}),
..ExitTest::default()
},
// Ensures that a validator cannot be exited twice in the same block.
invalid_duplicate,
ExitTest {
block_modifier: Box::new(|block| {
block_modifier: Box::new(|_, block| {
// Duplicate the exit
let exit = block.body.voluntary_exits[0].clone();
block.body.voluntary_exits.push(exit).unwrap();
let exit = block.body().voluntary_exits()[0].clone();
block.body_mut().voluntary_exits_mut().push(exit).unwrap();
}),
expected: Err(BlockProcessingError::ExitInvalid {
index: 1,
@@ -128,8 +139,10 @@ vectors_and_tests!(
// ```
invalid_validator_unknown,
ExitTest {
block_modifier: Box::new(|block| {
block.body.voluntary_exits[0].message.validator_index = VALIDATOR_COUNT as u64;
block_modifier: Box::new(|_, block| {
block.body_mut().voluntary_exits_mut()[0]
.message
.validator_index = VALIDATOR_COUNT as u64;
}),
expected: Err(BlockProcessingError::ExitInvalid {
index: 0,
@@ -147,9 +160,8 @@ vectors_and_tests!(
// ```
invalid_exit_already_initiated,
ExitTest {
builder_modifier: Box::new(|mut builder| {
builder.state.validators[0].exit_epoch = STATE_EPOCH + 1;
builder
state_modifier: Box::new(|state| {
state.validators_mut()[0].exit_epoch = STATE_EPOCH + 1;
}),
expected: Err(BlockProcessingError::ExitInvalid {
index: 0,
@@ -167,9 +179,8 @@ vectors_and_tests!(
// ```
invalid_not_active_before_activation_epoch,
ExitTest {
builder_modifier: Box::new(|mut builder| {
builder.state.validators[0].activation_epoch = builder.spec.far_future_epoch;
builder
state_modifier: Box::new(|state| {
state.validators_mut()[0].activation_epoch = E::default_spec().far_future_epoch;
}),
expected: Err(BlockProcessingError::ExitInvalid {
index: 0,
@@ -187,9 +198,8 @@ vectors_and_tests!(
// ```
invalid_not_active_after_exit_epoch,
ExitTest {
builder_modifier: Box::new(|mut builder| {
builder.state.validators[0].exit_epoch = STATE_EPOCH;
builder
state_modifier: Box::new(|state| {
state.validators_mut()[0].exit_epoch = STATE_EPOCH;
}),
expected: Err(BlockProcessingError::ExitInvalid {
index: 0,
@@ -286,10 +296,12 @@ vectors_and_tests!(
// ```
invalid_bad_signature,
ExitTest {
block_modifier: Box::new(|block| {
block_modifier: Box::new(|_, block| {
// Shift the validator index by 1 so that it's mismatched from the key that was
// used to sign.
block.body.voluntary_exits[0].message.validator_index = VALIDATOR_INDEX + 1;
block.body_mut().voluntary_exits_mut()[0]
.message
.validator_index = VALIDATOR_INDEX + 1;
}),
expected: Err(BlockProcessingError::ExitInvalid {
index: 0,
@@ -299,14 +311,14 @@ vectors_and_tests!(
}
);
#[cfg(test)]
#[cfg(all(test, not(debug_assertions)))]
mod custom_tests {
use super::*;
fn assert_exited(state: &BeaconState<E>, validator_index: usize) {
let spec = E::default_spec();
let validator = &state.validators[validator_index];
let validator = &state.validators()[validator_index];
assert_eq!(
validator.exit_epoch,
// This is correct until we exceed the churn limit. If that happens, we
@@ -330,10 +342,9 @@ mod custom_tests {
#[test]
fn valid_three() {
let state = ExitTest {
builder_modifier: Box::new(|builder| {
builder
.insert_exit(1, STATE_EPOCH)
.insert_exit(2, STATE_EPOCH)
block_modifier: Box::new(|harness, block| {
harness.add_voluntary_exit(block, 1, STATE_EPOCH);
harness.add_voluntary_exit(block, 2, STATE_EPOCH);
}),
..ExitTest::default()
}

View File

@@ -14,7 +14,7 @@ macro_rules! vectors_and_tests {
vec
}
#[cfg(test)]
#[cfg(all(test, not(debug_assertions)))]
mod tests {
use super::*;
$(

View File

@@ -2,15 +2,21 @@
mod macros;
mod exit;
use beacon_chain::{
store::StoreConfig,
test_utils::{BeaconChainHarness, EphemeralHarnessType},
};
use lazy_static::lazy_static;
use ssz::Encode;
use state_processing::test_utils::BlockProcessingBuilder;
use std::env;
use std::fs::{self, File};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::exit;
use types::MainnetEthSpec;
use types::{BeaconState, ChainSpec, EthSpec, SignedBeaconBlock};
use types::{
test_utils::generate_deterministic_keypairs, BeaconState, EthSpec, Keypair, SignedBeaconBlock,
};
use types::{Hash256, MainnetEthSpec, Slot};
type E = MainnetEthSpec;
@@ -19,6 +25,8 @@ pub const VALIDATOR_COUNT: usize = 64;
/// The base output directory for test vectors.
pub const BASE_VECTOR_DIR: &str = "vectors";
pub const SLOT_OFFSET: u64 = 1;
/// Writes all known test vectors to `CARGO_MANIFEST_DIR/vectors`.
fn main() {
match write_all_vectors() {
@@ -39,16 +47,36 @@ pub struct TestVector {
pub error: Option<String>,
}
/// Gets a `BlockProcessingBuilder` to be used in testing.
fn get_builder(
spec: &ChainSpec,
epoch_offset: u64,
num_validators: usize,
) -> BlockProcessingBuilder<MainnetEthSpec> {
// Set the state and block to be in the last slot of the `epoch_offset`th epoch.
let last_slot_of_epoch = (MainnetEthSpec::genesis_epoch() + epoch_offset)
.end_slot(MainnetEthSpec::slots_per_epoch());
BlockProcessingBuilder::new(num_validators, last_slot_of_epoch, &spec).build_caches()
lazy_static! {
/// A cached set of keys.
static ref KEYPAIRS: Vec<Keypair> = generate_deterministic_keypairs(VALIDATOR_COUNT);
}
fn get_harness<E: EthSpec>(
slot: Slot,
validator_count: usize,
) -> BeaconChainHarness<EphemeralHarnessType<E>> {
let harness = BeaconChainHarness::new_with_store_config(
E::default(),
None,
KEYPAIRS[0..validator_count].to_vec(),
StoreConfig::default(),
);
let skip_to_slot = slot - SLOT_OFFSET;
if skip_to_slot > Slot::new(0) {
let state = harness.get_current_state();
harness.add_attested_blocks_at_slots(
state,
Hash256::zero(),
(skip_to_slot.as_u64()..slot.as_u64())
.map(Slot::new)
.collect::<Vec<_>>()
.as_slice(),
(0..validator_count).collect::<Vec<_>>().as_slice(),
);
}
harness
}
/// Writes all vectors to file.