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

@@ -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)
}
}