diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index ad6ee7b0f..4d778f845 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -33,6 +33,15 @@ jobs: run: sudo npm install -g ganache-cli - name: Run tests in debug run: make test-debug + state-transition-vectors-ubuntu: + runs-on: ubuntu-latest + needs: cargo-fmt + steps: + - uses: actions/checkout@v1 + - name: Get latest version of stable Rust + run: rustup update stable + - name: Run state_transition_vectors in release. + run: make run-state-transition-tests ef-tests-ubuntu: runs-on: ubuntu-latest needs: cargo-fmt diff --git a/Cargo.lock b/Cargo.lock index 270a2756f..00b944519 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3884,6 +3884,15 @@ dependencies = [ "types 0.2.0", ] +[[package]] +name = "state_transition_vectors" +version = "0.1.0" +dependencies = [ + "eth2_ssz 0.1.2", + "state_processing 0.2.0", + "types 0.2.0", +] + [[package]] name = "static_assertions" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 75bfa5644..6de08c372 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ members = [ "tests/ef_tests", "tests/eth1_test_rig", "tests/node_test_rig", + "tests/state_transition_vectors", "lcli", "validator_client", "account_manager", diff --git a/Makefile b/Makefile index ecebc5198..011e0ce56 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ .PHONY: tests EF_TESTS = "tests/ef_tests" +STATE_TRANSITION_VECTORS = "tests/state_transition_vectors" # Builds the Lighthouse binary in release (optimized). # @@ -35,6 +36,10 @@ run-ef-tests: cargo test --release --manifest-path=$(EF_TESTS)/Cargo.toml --features "ef_tests" cargo test --release --manifest-path=$(EF_TESTS)/Cargo.toml --features "ef_tests,fake_crypto" +# Runs only the tests/state_transition_vectors tests. +run-state-transition-tests: + make -C $(STATE_TRANSITION_VECTORS) test + # Downloads and runs the EF test vectors. test-ef: make-ef-tests run-ef-tests @@ -66,3 +71,4 @@ arbitrary-fuzz: clean: cargo clean make -C $(EF_TESTS) clean + make -C $(STATE_TRANSITION_VECTORS) clean diff --git a/beacon_node/beacon_chain/tests/store_tests.rs b/beacon_node/beacon_chain/tests/store_tests.rs index 1e6f9e1ea..a5c7f00b0 100644 --- a/beacon_node/beacon_chain/tests/store_tests.rs +++ b/beacon_node/beacon_chain/tests/store_tests.rs @@ -1,4 +1,4 @@ -// #![cfg(not(debug_assertions))] +#![cfg(not(debug_assertions))] #[macro_use] extern crate lazy_static; diff --git a/eth2/operation_pool/src/lib.rs b/eth2/operation_pool/src/lib.rs index a99822519..9eb72ba55 100644 --- a/eth2/operation_pool/src/lib.rs +++ b/eth2/operation_pool/src/lib.rs @@ -218,7 +218,7 @@ impl OperationPool { state: &BeaconState, spec: &ChainSpec, ) -> Result<(), AttesterSlashingValidationError> { - verify_attester_slashing(state, &slashing, true, VerifySignatures::True, spec)?; + verify_attester_slashing(state, &slashing, VerifySignatures::True, spec)?; let id = Self::attester_slashing_id(&slashing, state, spec); self.attester_slashings.write().insert(id, slashing); Ok(()) diff --git a/eth2/state_processing/src/common/mod.rs b/eth2/state_processing/src/common/mod.rs index fa0f3d7e1..2d1ea2907 100644 --- a/eth2/state_processing/src/common/mod.rs +++ b/eth2/state_processing/src/common/mod.rs @@ -11,3 +11,24 @@ pub use get_base_reward::get_base_reward; pub use get_indexed_attestation::get_indexed_attestation; pub use initiate_validator_exit::initiate_validator_exit; pub use slash_validator::slash_validator; + +use safe_arith::{ArithError, SafeArith}; +use types::{BeaconState, EthSpec}; + +/// Increase the balance of a validator, erroring upon overflow, as per the spec. +/// +/// Spec v0.11.2 +pub fn increase_balance( + state: &mut BeaconState, + index: usize, + delta: u64, +) -> Result<(), ArithError> { + state.balances[index].safe_add_assign(delta) +} + +/// Decrease the balance of a validator, saturating upon overflow, as per the spec. +/// +/// Spec v0.11.2 +pub fn decrease_balance(state: &mut BeaconState, index: usize, delta: u64) { + state.balances[index] = state.balances[index].saturating_sub(delta); +} diff --git a/eth2/state_processing/src/common/slash_validator.rs b/eth2/state_processing/src/common/slash_validator.rs index 9c6731535..4460789ea 100644 --- a/eth2/state_processing/src/common/slash_validator.rs +++ b/eth2/state_processing/src/common/slash_validator.rs @@ -1,4 +1,4 @@ -use crate::common::initiate_validator_exit; +use crate::common::{decrease_balance, increase_balance, initiate_validator_exit}; use safe_arith::SafeArith; use std::cmp; use types::{BeaconStateError as Error, *}; @@ -32,9 +32,10 @@ pub fn slash_validator( .get_slashings(epoch)? .safe_add(validator_effective_balance)?, )?; - safe_sub_assign!( - state.balances[slashed_index], - validator_effective_balance.safe_div(spec.min_slashing_penalty_quotient)? + decrease_balance( + state, + slashed_index, + validator_effective_balance.safe_div(spec.min_slashing_penalty_quotient)?, ); // Apply proposer and whistleblower rewards @@ -44,11 +45,12 @@ pub fn slash_validator( validator_effective_balance.safe_div(spec.whistleblower_reward_quotient)?; let proposer_reward = whistleblower_reward.safe_div(spec.proposer_reward_quotient)?; - safe_add_assign!(state.balances[proposer_index], proposer_reward); - safe_add_assign!( - state.balances[whistleblower_index], - whistleblower_reward.saturating_sub(proposer_reward) - ); + increase_balance(state, proposer_index, proposer_reward)?; + increase_balance( + state, + whistleblower_index, + whistleblower_reward.safe_sub(proposer_reward)?, + )?; Ok(()) } diff --git a/eth2/state_processing/src/macros.rs b/eth2/state_processing/src/macros.rs index 9683b21ad..66070353d 100644 --- a/eth2/state_processing/src/macros.rs +++ b/eth2/state_processing/src/macros.rs @@ -13,14 +13,3 @@ macro_rules! block_verify { } }; } - -macro_rules! safe_add_assign { - ($a: expr, $b: expr) => { - $a = $a.saturating_add($b); - }; -} -macro_rules! safe_sub_assign { - ($a: expr, $b: expr) => { - $a = $a.saturating_sub($b); - }; -} diff --git a/eth2/state_processing/src/per_block_processing.rs b/eth2/state_processing/src/per_block_processing.rs index 7d47e80ce..edca41458 100644 --- a/eth2/state_processing/src/per_block_processing.rs +++ b/eth2/state_processing/src/per_block_processing.rs @@ -1,4 +1,4 @@ -use crate::common::{initiate_validator_exit, slash_validator}; +use crate::common::{increase_balance, initiate_validator_exit, slash_validator}; use errors::{BlockOperationError, BlockProcessingError, HeaderInvalid, IntoWithIndex}; use rayon::prelude::*; use safe_arith::{ArithError, SafeArith}; @@ -321,37 +321,9 @@ pub fn process_attester_slashings( verify_signatures: VerifySignatures, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { - // Verify the `IndexedAttestation`s in parallel (these are the resource-consuming objects, not - // the `AttesterSlashing`s themselves). - let mut indexed_attestations: Vec<&_> = - Vec::with_capacity(attester_slashings.len().safe_mul(2)?); - for attester_slashing in attester_slashings { - indexed_attestations.push(&attester_slashing.attestation_1); - indexed_attestations.push(&attester_slashing.attestation_2); - } - - // Verify indexed attestations in parallel. - indexed_attestations - .par_iter() - .enumerate() - .try_for_each(|(i, indexed_attestation)| { - is_valid_indexed_attestation(&state, indexed_attestation, verify_signatures, spec) - .map_err(|e| e.into_with_index(i)) - })?; - let all_indexed_attestations_have_been_checked = true; - - // Gather the indexed indices and preform the final verification and update the state in series. for (i, attester_slashing) in attester_slashings.iter().enumerate() { - let should_verify_indexed_attestations = !all_indexed_attestations_have_been_checked; - - verify_attester_slashing( - &state, - &attester_slashing, - should_verify_indexed_attestations, - verify_signatures, - spec, - ) - .map_err(|e| e.into_with_index(i))?; + verify_attester_slashing(&state, &attester_slashing, verify_signatures, spec) + .map_err(|e| e.into_with_index(i))?; let slashable_indices = get_slashable_indices(&state, &attester_slashing).map_err(|e| e.into_with_index(i))?; @@ -379,18 +351,13 @@ pub fn process_attestations( // Ensure the previous epoch cache exists. state.build_committee_cache(RelativeEpoch::Previous, spec)?; - // Verify attestations in parallel. - attestations - .par_iter() - .enumerate() - .try_for_each(|(i, attestation)| { - verify_attestation_for_block_inclusion(state, attestation, verify_signatures, spec) - .map_err(|e| e.into_with_index(i)) - })?; - - // Update the state in series. let proposer_index = state.get_beacon_proposer_index(state.slot, spec)? as u64; - for attestation in attestations { + + // Verify and apply each attestation. + for (i, attestation) in attestations.iter().enumerate() { + verify_attestation_for_block_inclusion(state, attestation, verify_signatures, spec) + .map_err(|e| e.into_with_index(i))?; + let pending_attestation = PendingAttestation { aggregation_bits: attestation.aggregation_bits.clone(), data: attestation.data.clone(), @@ -489,7 +456,7 @@ pub fn process_deposit( if let Some(index) = validator_index { // Update the existing validator balance. - safe_add_assign!(state.balances[index as usize], amount); + increase_balance(state, index as usize, amount)?; } else { // The signature should be checked for new validators. Return early for a bad // signature. @@ -530,18 +497,12 @@ pub fn process_exits( verify_signatures: VerifySignatures, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { - // Verify exits in parallel. - voluntary_exits - .par_iter() - .enumerate() - .try_for_each(|(i, exit)| { - verify_exit(&state, exit, verify_signatures, spec).map_err(|e| e.into_with_index(i)) - })?; + // Verify and apply each exit in series. We iterate in series because higher-index exits may + // become invalid due to the application of lower-index ones. + for (i, exit) in voluntary_exits.into_iter().enumerate() { + verify_exit(&state, exit, verify_signatures, spec).map_err(|e| e.into_with_index(i))?; - // Update the state in series. - for exit in voluntary_exits { initiate_validator_exit(state, exit.message.validator_index as usize, spec)?; } - Ok(()) } diff --git a/eth2/state_processing/src/per_block_processing/block_processing_builder.rs b/eth2/state_processing/src/per_block_processing/block_processing_builder.rs index 5c7961611..1fca43f68 100644 --- a/eth2/state_processing/src/per_block_processing/block_processing_builder.rs +++ b/eth2/state_processing/src/per_block_processing/block_processing_builder.rs @@ -1,37 +1,38 @@ -use std::convert::TryInto; use tree_hash::TreeHash; use types::test_utils::{ - AttestationTestTask, AttesterSlashingTestTask, DepositTestTask, ExitTestTask, - ProposerSlashingTestTask, TestingBeaconBlockBuilder, TestingBeaconStateBuilder, + AttestationTestTask, AttesterSlashingTestTask, DepositTestTask, ProposerSlashingTestTask, + TestingAttestationDataBuilder, TestingBeaconBlockBuilder, TestingBeaconStateBuilder, }; use types::*; -pub struct BlockProcessingBuilder { - pub state_builder: TestingBeaconStateBuilder, +pub struct BlockProcessingBuilder<'a, T: EthSpec> { + pub state: BeaconState, + pub keypairs: Vec, pub block_builder: TestingBeaconBlockBuilder, - pub num_validators: usize, + pub spec: &'a ChainSpec, } -impl BlockProcessingBuilder { - pub fn new(num_validators: usize, spec: &ChainSpec) -> Self { - let state_builder = +impl<'a, T: EthSpec> BlockProcessingBuilder<'a, T> { + pub fn new(num_validators: usize, state_slot: Slot, spec: &'a ChainSpec) -> Self { + let mut state_builder = TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(num_validators, &spec); + state_builder.teleport_to_slot(state_slot); + let (state, keypairs) = state_builder.build(); let block_builder = TestingBeaconBlockBuilder::new(spec); Self { - state_builder, + state, + keypairs, block_builder, - num_validators: 0, + spec, } } - pub fn set_slot(&mut self, slot: Slot) { - self.state_builder.teleport_to_slot(slot); - } - - pub fn build_caches(&mut self, spec: &ChainSpec) { - // Builds all caches; benches will not contain shuffling/committee building times. - self.state_builder.build_caches(&spec).unwrap(); + pub fn build_caches(mut self) -> Self { + self.state + .build_all_caches(self.spec) + .expect("caches build OK"); + self } pub fn build_with_n_deposits( @@ -42,7 +43,7 @@ impl BlockProcessingBuilder { previous_block_root: Option, spec: &ChainSpec, ) -> (SignedBeaconBlock, BeaconState) { - let (mut state, keypairs) = self.state_builder.build(); + let (mut state, keypairs) = (self.state, self.keypairs); let builder = &mut self.block_builder; @@ -89,73 +90,73 @@ impl BlockProcessingBuilder { (block, state) } - pub fn build_with_n_exits( - mut self, - num_exits: usize, - test_task: ExitTestTask, - randao_sk: Option, - previous_block_root: Option, - spec: &ChainSpec, - ) -> (SignedBeaconBlock, BeaconState) { - let (mut state, keypairs) = self.state_builder.build(); - let builder = &mut self.block_builder; - - builder.set_slot(state.slot); - - match previous_block_root { - Some(root) => builder.set_parent_root(root), - None => builder.set_parent_root(state.latest_block_header.tree_hash_root()), - } - - let proposer_index = state.get_beacon_proposer_index(state.slot, spec).unwrap(); - let keypair = &keypairs[proposer_index]; - - builder.set_proposer_index(proposer_index as u64); - - match randao_sk { - Some(sk) => { - builder.set_randao_reveal(&sk, &state.fork, state.genesis_validators_root, spec) - } - None => builder.set_randao_reveal( - &keypair.sk, - &state.fork, - state.genesis_validators_root, - spec, - ), - } - match test_task { - ExitTestTask::AlreadyInitiated => { - for _ in 0..2 { - self.block_builder.insert_exit( - test_task, - &mut state, - (0 as usize).try_into().unwrap(), - &keypairs[0].sk, - spec, - ) - } - } - _ => { - for (i, keypair) in keypairs.iter().take(num_exits).enumerate() { - self.block_builder.insert_exit( - test_task, - &mut state, - (i as usize).try_into().unwrap(), - &keypair.sk, - spec, - ); - } - } - } - - let block = self.block_builder.build( - &keypair.sk, - &state.fork, - state.genesis_validators_root, - spec, + /// Insert a signed `VoluntaryIndex` for the given validator at the given `exit_epoch`. + pub fn insert_exit(mut self, validator_index: u64, exit_epoch: Epoch) -> Self { + self.block_builder.insert_exit( + validator_index, + exit_epoch, + &self.keypairs[validator_index as usize].sk, + &self.state, + self.spec, ); + self + } - (block, state) + /// Insert an attestation for the given slot and index. + /// + /// It will be signed by all validators for which `should_sign` returns `true` + /// when called with `(committee_position, validator_index)`. + // TODO: consider using this pattern to replace the TestingAttestationBuilder + pub fn insert_attestation( + mut self, + slot: Slot, + index: u64, + mut should_sign: impl FnMut(usize, usize) -> bool, + ) -> Self { + let committee = self.state.get_beacon_committee(slot, index).unwrap(); + let data = TestingAttestationDataBuilder::new( + AttestationTestTask::Valid, + &self.state, + index, + slot, + self.spec, + ) + .build(); + + let mut attestation = Attestation { + aggregation_bits: BitList::with_capacity(committee.committee.len()).unwrap(), + data, + signature: AggregateSignature::new(), + }; + + for (i, &validator_index) in committee.committee.into_iter().enumerate() { + if should_sign(i, validator_index) { + attestation + .sign( + &self.keypairs[validator_index].sk, + i, + &self.state.fork, + self.state.genesis_validators_root, + self.spec, + ) + .unwrap(); + } + } + + self.block_builder + .block + .body + .attestations + .push(attestation) + .unwrap(); + + self + } + + /// Apply a mutation to the `BeaconBlock` before signing. + pub fn modify(mut self, f: impl FnOnce(&mut BeaconBlock)) -> Self { + self.block_builder.modify(f); + self } pub fn build_with_n_attestations( @@ -166,7 +167,7 @@ impl BlockProcessingBuilder { previous_block_root: Option, spec: &ChainSpec, ) -> (SignedBeaconBlock, BeaconState) { - let (state, keypairs) = self.state_builder.build(); + let (state, keypairs) = (self.state, self.keypairs); let builder = &mut self.block_builder; builder.set_slot(state.slot); @@ -221,7 +222,7 @@ impl BlockProcessingBuilder { previous_block_root: Option, spec: &ChainSpec, ) -> (SignedBeaconBlock, BeaconState) { - let (state, keypairs) = self.state_builder.build(); + let (state, keypairs) = (self.state, self.keypairs); let builder = &mut self.block_builder; builder.set_slot(state.slot); @@ -283,7 +284,7 @@ impl BlockProcessingBuilder { previous_block_root: Option, spec: &ChainSpec, ) -> (SignedBeaconBlock, BeaconState) { - let (state, keypairs) = self.state_builder.build(); + let (state, keypairs) = (self.state, self.keypairs); let builder = &mut self.block_builder; builder.set_slot(state.slot); @@ -332,13 +333,15 @@ impl BlockProcessingBuilder { (block, state) } + // NOTE: could remove optional args + // NOTE: could return keypairs as well pub fn build( mut self, randao_sk: Option, previous_block_root: Option, - spec: &ChainSpec, ) -> (SignedBeaconBlock, BeaconState) { - let (state, keypairs) = self.state_builder.build(); + let (state, keypairs) = (self.state, self.keypairs); + let spec = self.spec; let builder = &mut self.block_builder; builder.set_slot(state.slot); diff --git a/eth2/state_processing/src/per_block_processing/tests.rs b/eth2/state_processing/src/per_block_processing/tests.rs index dce2d352c..55bc459cd 100644 --- a/eth2/state_processing/src/per_block_processing/tests.rs +++ b/eth2/state_processing/src/per_block_processing/tests.rs @@ -4,15 +4,13 @@ use super::block_processing_builder::BlockProcessingBuilder; use super::errors::*; use crate::{per_block_processing, BlockSignatureStrategy}; use types::test_utils::{ - AttestationTestTask, AttesterSlashingTestTask, DepositTestTask, ExitTestTask, - ProposerSlashingTestTask, + AttestationTestTask, AttesterSlashingTestTask, DepositTestTask, ProposerSlashingTestTask, }; use types::*; pub const NUM_DEPOSITS: u64 = 1; pub const VALIDATOR_COUNT: usize = 64; -pub const SLOT_OFFSET: u64 = 4; -pub const EXIT_SLOT_OFFSET: u64 = 2048; +pub const EPOCH_OFFSET: u64 = 4; pub const NUM_ATTESTATIONS: u64 = 1; type E = MainnetEthSpec; @@ -20,8 +18,8 @@ type E = MainnetEthSpec; #[test] fn valid_block_ok() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); - let (block, mut state) = builder.build(None, None, &spec); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); + let (block, mut state) = builder.build(None, None); let result = per_block_processing( &mut state, @@ -37,8 +35,8 @@ fn valid_block_ok() { #[test] fn invalid_block_header_state_slot() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); - let (mut block, mut state) = builder.build(None, None, &spec); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); + let (mut block, mut state) = builder.build(None, None); state.slot = Slot::new(133_713); block.message.slot = Slot::new(424_242); @@ -62,9 +60,9 @@ fn invalid_block_header_state_slot() { #[test] fn invalid_parent_block_root() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let invalid_parent_root = Hash256::from([0xAA; 32]); - let (block, mut state) = builder.build(None, Some(invalid_parent_root), &spec); + let (block, mut state) = builder.build(None, Some(invalid_parent_root)); let result = per_block_processing( &mut state, @@ -88,8 +86,8 @@ fn invalid_parent_block_root() { #[test] fn invalid_block_signature() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); - let (block, mut state) = builder.build(None, None, &spec); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); + let (block, mut state) = builder.build(None, None); // sign the block with a keypair that is not the expected proposer let keypair = Keypair::random(); @@ -121,11 +119,11 @@ fn invalid_block_signature() { #[test] fn invalid_randao_reveal_signature() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); // sign randao reveal with random keypair let keypair = Keypair::random(); - let (block, mut state) = builder.build(Some(keypair.sk), None, &spec); + let (block, mut state) = builder.build(Some(keypair.sk), None); let result = per_block_processing( &mut state, @@ -142,7 +140,7 @@ fn invalid_randao_reveal_signature() { #[test] fn valid_4_deposits() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = DepositTestTask::Valid; let (block, mut state) = builder.build_with_n_deposits(4, test_task, None, None, &spec); @@ -162,7 +160,7 @@ fn valid_4_deposits() { #[test] fn invalid_deposit_deposit_count_too_big() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = DepositTestTask::Valid; let (block, mut state) = @@ -192,7 +190,7 @@ fn invalid_deposit_deposit_count_too_big() { #[test] fn invalid_deposit_count_too_small() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = DepositTestTask::Valid; let (block, mut state) = @@ -222,7 +220,7 @@ fn invalid_deposit_count_too_small() { #[test] fn invalid_deposit_bad_merkle_proof() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = DepositTestTask::Valid; let (block, mut state) = @@ -254,7 +252,7 @@ fn invalid_deposit_bad_merkle_proof() { #[test] fn invalid_deposit_wrong_pubkey() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = DepositTestTask::BadPubKey; let (block, mut state) = @@ -275,7 +273,7 @@ fn invalid_deposit_wrong_pubkey() { #[test] fn invalid_deposit_wrong_sig() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = DepositTestTask::BadSig; let (block, mut state) = @@ -296,7 +294,7 @@ fn invalid_deposit_wrong_sig() { #[test] fn invalid_deposit_invalid_pub_key() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = DepositTestTask::InvalidPubKey; let (block, mut state) = @@ -314,241 +312,10 @@ fn invalid_deposit_invalid_pub_key() { assert_eq!(result, Ok(())); } -#[test] -fn valid_insert_3_exits() { - use std::cmp::max; - - let spec = MainnetEthSpec::default_spec(); - let num_exits = 3; - let num_validators = max(VALIDATOR_COUNT, num_exits); - let test_task = ExitTestTask::Valid; - let builder = get_builder(&spec, EXIT_SLOT_OFFSET, num_validators); - - let (block, mut state) = builder.build_with_n_exits(num_exits, test_task, None, None, &spec); - let result = per_block_processing( - &mut state, - &block, - None, - BlockSignatureStrategy::VerifyIndividual, - &spec, - ); - - // Expecting Ok because these are valid exits. - assert_eq!(result, Ok(())); -} - -#[test] -fn invalid_exit_validator_unknown() { - use std::cmp::max; - - let spec = MainnetEthSpec::default_spec(); - let num_exits = 1; - let test_task = ExitTestTask::ValidatorUnknown; - let num_validators = max(VALIDATOR_COUNT, num_exits); - let builder = get_builder(&spec, EXIT_SLOT_OFFSET, num_validators); - - let (block, mut state) = builder.build_with_n_exits(num_exits, test_task, None, None, &spec); - - let result = per_block_processing( - &mut state, - &block, - None, - BlockSignatureStrategy::VerifyIndividual, - &spec, - ); - - // Expecting Validator Unknwon because the exit index is incorrect - assert_eq!( - result, - Err(BlockProcessingError::ExitInvalid { - index: 0, - reason: ExitInvalid::ValidatorUnknown(4242), - }) - ); -} - -#[test] -fn invalid_exit_already_exited() { - use std::cmp::max; - - let spec = MainnetEthSpec::default_spec(); - let num_exits = 1; - let test_task = ExitTestTask::AlreadyExited; - let num_validators = max(VALIDATOR_COUNT, num_exits); - let builder = get_builder(&spec, EXIT_SLOT_OFFSET, num_validators); - - let (block, mut state) = builder.build_with_n_exits(num_exits, test_task, None, None, &spec); - let result = per_block_processing( - &mut state, - &block, - None, - BlockSignatureStrategy::VerifyIndividual, - &spec, - ); - - // Expecting AlreadyExited because we manually set the exit_epoch to be different than far_future_epoch. - assert_eq!( - result, - Err(BlockProcessingError::ExitInvalid { - index: 0, - reason: ExitInvalid::AlreadyExited(0), - }) - ); -} - -/* FIXME: needs updating for v0.9 -#[test] -fn invalid_exit_not_active() { - use std::cmp::max; - - let spec = MainnetEthSpec::default_spec(); - let num_exits = 1; - let test_task = ExitTestTask::NotActive; - let num_validators = max(VALIDATOR_COUNT, num_exits); - let builder = get_builder(&spec, EXIT_SLOT_OFFSET, num_validators); - - let (block, mut state) = builder.build_with_n_exits(num_exits, test_task, None, None, &spec); - let result = per_block_processing( - &mut state, - &block, - None, - BlockSignatureStrategy::VerifyIndividual, - &spec, - ); - - // Expecting NotActive because we manually set the activation_epoch to be in the future - assert_eq!( - result, - Err(BlockProcessingError::ExitInvalid { - index: 0, - reason: ExitInvalid::NotActive(0), - }) - ); -} -*/ - -#[test] -fn invalid_exit_already_initiated() { - use std::cmp::max; - - let spec = MainnetEthSpec::default_spec(); - let num_exits = 1; - let test_task = ExitTestTask::AlreadyInitiated; - let num_validators = max(VALIDATOR_COUNT, num_exits); - let builder = get_builder(&spec, EXIT_SLOT_OFFSET, num_validators); - - let (block, mut state) = builder.build_with_n_exits(num_exits, test_task, None, None, &spec); - - let result = per_block_processing( - &mut state, - &block, - None, - BlockSignatureStrategy::VerifyIndividual, - &spec, - ); - - // Expecting Ok(()) even though we inserted the same exit twice - assert_eq!(result, Ok(())); -} - -#[test] -fn invalid_exit_future_epoch() { - use std::cmp::max; - - let spec = MainnetEthSpec::default_spec(); - let num_exits = 1; - let test_task = ExitTestTask::FutureEpoch; - let num_validators = max(VALIDATOR_COUNT, num_exits); - let builder = get_builder(&spec, EXIT_SLOT_OFFSET, num_validators); - - let (block, mut state) = builder.build_with_n_exits(num_exits, test_task, None, None, &spec); - - let result = per_block_processing( - &mut state, - &block, - None, - BlockSignatureStrategy::VerifyIndividual, - &spec, - ); - - // Expecting FutureEpoch because we set the exit_epoch to be far_future_epoch - assert_eq!( - result, - Err(BlockProcessingError::ExitInvalid { - index: 0, - reason: ExitInvalid::FutureEpoch { - state: Epoch::from(2048 as u64), - exit: spec.far_future_epoch - } - }) - ); -} - -#[test] -fn invalid_exit_too_young() { - use std::cmp::max; - - let spec = MainnetEthSpec::default_spec(); - let num_exits = 1; - let test_task = ExitTestTask::Valid; - let num_validators = max(VALIDATOR_COUNT, num_exits); - let builder = get_builder(&spec, SLOT_OFFSET, num_validators); - - let (block, mut state) = builder.build_with_n_exits(num_exits, test_task, None, None, &spec); - let result = per_block_processing( - &mut state, - &block, - None, - BlockSignatureStrategy::VerifyIndividual, - &spec, - ); - - // Expecting TooYoung because validator has not been active for long enough when trying to exit - assert_eq!( - result, - Err(BlockProcessingError::ExitInvalid { - index: 0, - reason: ExitInvalid::TooYoungToExit { - current_epoch: Epoch::from(SLOT_OFFSET), - earliest_exit_epoch: Epoch::from(2048 as u64) - }, - }) - ); -} - -#[test] -fn invalid_exit_bad_signature() { - use std::cmp::max; - - let spec = MainnetEthSpec::default_spec(); - let num_exits = 1; - let test_task = ExitTestTask::BadSignature; - let num_validators = max(VALIDATOR_COUNT, num_exits); - let builder = get_builder(&spec, EXIT_SLOT_OFFSET, num_validators); - - let (block, mut state) = builder.build_with_n_exits(num_exits, test_task, None, None, &spec); - let result = per_block_processing( - &mut state, - &block, - None, - BlockSignatureStrategy::VerifyIndividual, - &spec, - ); - - // Expecting Bad Signature because we signed with a different secret key than the correct one. - assert_eq!( - result, - Err(BlockProcessingError::ExitInvalid { - index: 0, - reason: ExitInvalid::BadSignature, - }) - ); -} - #[test] fn valid_attestations() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = AttestationTestTask::Valid; let (block, mut state) = builder.build_with_n_attestations(test_task, NUM_ATTESTATIONS, None, None, &spec); @@ -565,14 +332,18 @@ fn valid_attestations() { assert_eq!(result, Ok(())); } -/* FIXME: needs updating for v0.9 #[test] -fn invalid_attestation_no_committee_for_shard() { +fn invalid_attestation_no_committee_for_index() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); - let test_task = AttestationTestTask::NoCommiteeForShard; - let (block, mut state) = - builder.build_with_n_attestations(test_task, NUM_ATTESTATIONS, None, None, &spec); + let slot = Epoch::new(EPOCH_OFFSET).start_slot(E::slots_per_epoch()); + let builder = + get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT).insert_attestation(slot, 0, |_, _| true); + let committee_index = builder.state.get_committee_count_at_slot(slot).unwrap(); + let (block, mut state) = builder + .modify(|block| { + block.body.attestations[0].data.index = committee_index; + }) + .build(None, None); let result = per_block_processing( &mut state, @@ -582,23 +353,20 @@ fn invalid_attestation_no_committee_for_shard() { &spec, ); - // Expecting NoCommiteeForShard because we manually set the crosslink's shard to be invalid + // Expecting NoCommitee because we manually set the attestation's index to be invalid assert_eq!( result, - Err(BlockProcessingError::BeaconStateError( - BeaconStateError::NoCommittee { - slot: Slot::new(0), - index: 0 - } - )) + Err(BlockProcessingError::AttestationInvalid { + index: 0, + reason: AttestationInvalid::BadCommitteeIndex + }) ); } -*/ #[test] fn invalid_attestation_wrong_justified_checkpoint() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = AttestationTestTask::WrongJustifiedCheckpoint; let (block, mut state) = builder.build_with_n_attestations(test_task, NUM_ATTESTATIONS, None, None, &spec); @@ -635,7 +403,7 @@ fn invalid_attestation_wrong_justified_checkpoint() { #[test] fn invalid_attestation_bad_indexed_attestation_bad_signature() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = AttestationTestTask::BadIndexedAttestationBadSignature; let (block, mut state) = builder.build_with_n_attestations(test_task, NUM_ATTESTATIONS, None, None, &spec); @@ -663,7 +431,7 @@ fn invalid_attestation_bad_indexed_attestation_bad_signature() { #[test] fn invalid_attestation_bad_aggregation_bitfield_len() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = AttestationTestTask::BadAggregationBitfieldLen; let (block, mut state) = builder.build_with_n_attestations(test_task, NUM_ATTESTATIONS, None, None, &spec); @@ -688,7 +456,7 @@ fn invalid_attestation_bad_aggregation_bitfield_len() { #[test] fn invalid_attestation_bad_signature() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, 97); // minimal number of required validators for this test + let builder = get_builder(&spec, EPOCH_OFFSET, 97); // minimal number of required validators for this test let test_task = AttestationTestTask::BadSignature; let (block, mut state) = builder.build_with_n_attestations(test_task, NUM_ATTESTATIONS, None, None, &spec); @@ -715,7 +483,7 @@ fn invalid_attestation_bad_signature() { #[test] fn invalid_attestation_included_too_early() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = AttestationTestTask::IncludedTooEarly; let (block, mut state) = builder.build_with_n_attestations(test_task, NUM_ATTESTATIONS, None, None, &spec); @@ -746,7 +514,7 @@ fn invalid_attestation_included_too_early() { fn invalid_attestation_included_too_late() { let spec = MainnetEthSpec::default_spec(); // note to maintainer: might need to increase validator count if we get NoCommittee - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = AttestationTestTask::IncludedTooLate; let (block, mut state) = builder.build_with_n_attestations(test_task, NUM_ATTESTATIONS, None, None, &spec); @@ -775,7 +543,7 @@ fn invalid_attestation_included_too_late() { fn invalid_attestation_target_epoch_slot_mismatch() { let spec = MainnetEthSpec::default_spec(); // note to maintainer: might need to increase validator count if we get NoCommittee - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = AttestationTestTask::TargetEpochSlotMismatch; let (block, mut state) = builder.build_with_n_attestations(test_task, NUM_ATTESTATIONS, None, None, &spec); @@ -801,45 +569,10 @@ fn invalid_attestation_target_epoch_slot_mismatch() { ); } -/* FIXME: needs updating for v0.9 -#[test] -fn invalid_attestation_bad_shard() { - let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); - let test_task = AttestationTestTask::BadShard; - let (block, mut state) = - builder.build_with_n_attestations(test_task, NUM_ATTESTATIONS, None, None, &spec); - - let result = per_block_processing( - &mut state, - &block, - None, - BlockSignatureStrategy::VerifyIndividual, - &spec, - ); - - // Expecting BadShard or NoCommittee because the shard number is higher than ShardCount - assert!( - result - == Err(BlockProcessingError::AttestationInvalid { - index: 0, - reason: AttestationInvalid::BadShard - }) - || result - == Err(BlockProcessingError::BeaconStateError( - BeaconStateError::NoCommittee { - slot: Slot::new(0), - index: 0 - } - )) - ); -} -*/ - #[test] fn valid_insert_attester_slashing() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = AttesterSlashingTestTask::Valid; let num_attester_slashings = 1; let (block, mut state) = @@ -860,7 +593,7 @@ fn valid_insert_attester_slashing() { #[test] fn invalid_attester_slashing_not_slashable() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = AttesterSlashingTestTask::NotSlashable; let num_attester_slashings = 1; let (block, mut state) = @@ -886,7 +619,7 @@ fn invalid_attester_slashing_not_slashable() { #[test] fn invalid_attester_slashing_1_invalid() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = AttesterSlashingTestTask::IndexedAttestation1Invalid; let num_attester_slashings = 1; let (block, mut state) = @@ -902,17 +635,21 @@ fn invalid_attester_slashing_1_invalid() { assert_eq!( result, - Err(BlockProcessingError::IndexedAttestationInvalid { - index: 0, - reason: IndexedAttestationInvalid::BadValidatorIndicesOrdering(0) - }) + Err( + BlockOperationError::Invalid(AttesterSlashingInvalid::IndexedAttestation1Invalid( + BlockOperationError::Invalid( + IndexedAttestationInvalid::BadValidatorIndicesOrdering(0) + ) + )) + .into_with_index(0) + ) ); } #[test] fn invalid_attester_slashing_2_invalid() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = AttesterSlashingTestTask::IndexedAttestation2Invalid; let num_attester_slashings = 1; let (block, mut state) = @@ -928,17 +665,21 @@ fn invalid_attester_slashing_2_invalid() { assert_eq!( result, - Err(BlockProcessingError::IndexedAttestationInvalid { - index: 1, - reason: IndexedAttestationInvalid::BadValidatorIndicesOrdering(0) - }) + Err( + BlockOperationError::Invalid(AttesterSlashingInvalid::IndexedAttestation2Invalid( + BlockOperationError::Invalid( + IndexedAttestationInvalid::BadValidatorIndicesOrdering(0) + ) + )) + .into_with_index(0) + ) ); } #[test] fn valid_insert_proposer_slashing() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = ProposerSlashingTestTask::Valid; let (block, mut state) = builder.build_with_proposer_slashing(test_task, 1, None, None, &spec); @@ -957,7 +698,7 @@ fn valid_insert_proposer_slashing() { #[test] fn invalid_proposer_slashing_proposals_identical() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = ProposerSlashingTestTask::ProposalsIdentical; let (block, mut state) = builder.build_with_proposer_slashing(test_task, 1, None, None, &spec); @@ -981,7 +722,7 @@ fn invalid_proposer_slashing_proposals_identical() { #[test] fn invalid_proposer_slashing_proposer_unknown() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = ProposerSlashingTestTask::ProposerUnknown; let (block, mut state) = builder.build_with_proposer_slashing(test_task, 1, None, None, &spec); @@ -1006,7 +747,7 @@ fn invalid_proposer_slashing_proposer_unknown() { #[test] fn invalid_proposer_slashing_not_slashable() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = ProposerSlashingTestTask::ProposerNotSlashable; let (block, mut state) = builder.build_with_proposer_slashing(test_task, 1, None, None, &spec); @@ -1032,7 +773,7 @@ fn invalid_proposer_slashing_not_slashable() { #[test] fn invalid_proposer_slashing_duplicate_slashing() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = ProposerSlashingTestTask::Valid; let (mut block, mut state) = builder.build_with_proposer_slashing(test_task, 1, None, None, &spec); @@ -1068,7 +809,7 @@ fn invalid_proposer_slashing_duplicate_slashing() { #[test] fn invalid_bad_proposal_1_signature() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = ProposerSlashingTestTask::BadProposal1Signature; let (block, mut state) = builder.build_with_proposer_slashing(test_task, 1, None, None, &spec); @@ -1093,7 +834,7 @@ fn invalid_bad_proposal_1_signature() { #[test] fn invalid_bad_proposal_2_signature() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = ProposerSlashingTestTask::BadProposal2Signature; let (block, mut state) = builder.build_with_proposer_slashing(test_task, 1, None, None, &spec); @@ -1118,7 +859,7 @@ fn invalid_bad_proposal_2_signature() { #[test] fn invalid_proposer_slashing_proposal_epoch_mismatch() { let spec = MainnetEthSpec::default_spec(); - let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT); let test_task = ProposerSlashingTestTask::ProposalEpochMismatch; let (block, mut state) = builder.build_with_proposer_slashing(test_task, 1, None, None, &spec); @@ -1145,15 +886,11 @@ fn invalid_proposer_slashing_proposal_epoch_mismatch() { fn get_builder( spec: &ChainSpec, - slot_offset: u64, + epoch_offset: u64, num_validators: usize, ) -> BlockProcessingBuilder { - let mut builder = BlockProcessingBuilder::new(num_validators, &spec); - - // Set the state and block to be in the last slot of the `slot_offset`th epoch. - let last_slot_of_epoch = - (MainnetEthSpec::genesis_epoch() + slot_offset).end_slot(MainnetEthSpec::slots_per_epoch()); - builder.set_slot(last_slot_of_epoch); - builder.build_caches(&spec); - builder + // 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() } diff --git a/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs b/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs index c1e33dd4b..21f60fd23 100644 --- a/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs +++ b/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs @@ -10,16 +10,16 @@ fn error(reason: Invalid) -> BlockOperationError { BlockOperationError::invalid(reason) } -/// Indicates if an `AttesterSlashing` is valid to be included in a block in the current epoch of the given -/// state. +/// Indicates if an `AttesterSlashing` is valid to be included in a block in the current epoch of +/// the given state. /// -/// Returns `Ok(())` if the `AttesterSlashing` is valid, otherwise indicates the reason for invalidity. +/// Returns `Ok(())` if the `AttesterSlashing` is valid, otherwise indicates the reason for +/// invalidity. /// /// Spec v0.11.1 pub fn verify_attester_slashing( state: &BeaconState, attester_slashing: &AttesterSlashing, - should_verify_indexed_attestations: bool, verify_signatures: VerifySignatures, spec: &ChainSpec, ) -> Result<()> { @@ -33,12 +33,10 @@ pub fn verify_attester_slashing( Invalid::NotSlashable ); - if should_verify_indexed_attestations { - is_valid_indexed_attestation(state, &attestation_1, verify_signatures, spec) - .map_err(|e| error(Invalid::IndexedAttestation1Invalid(e)))?; - is_valid_indexed_attestation(state, &attestation_2, verify_signatures, spec) - .map_err(|e| error(Invalid::IndexedAttestation2Invalid(e)))?; - } + is_valid_indexed_attestation(state, &attestation_1, verify_signatures, spec) + .map_err(|e| error(Invalid::IndexedAttestation1Invalid(e)))?; + is_valid_indexed_attestation(state, &attestation_2, verify_signatures, spec) + .map_err(|e| error(Invalid::IndexedAttestation2Invalid(e)))?; Ok(()) } diff --git a/eth2/state_processing/src/per_epoch_processing/process_slashings.rs b/eth2/state_processing/src/per_epoch_processing/process_slashings.rs index c291d3f09..e8b6a5f3c 100644 --- a/eth2/state_processing/src/per_epoch_processing/process_slashings.rs +++ b/eth2/state_processing/src/per_epoch_processing/process_slashings.rs @@ -26,7 +26,8 @@ pub fn process_slashings( .safe_div(total_balance)? .safe_mul(increment)?; - safe_sub_assign!(state.balances[index], penalty); + // Equivalent to `decrease_balance(state, index, penalty)`, but avoids borrowing `state`. + state.balances[index] = state.balances[index].saturating_sub(penalty); } } diff --git a/eth2/state_processing/src/test_utils.rs b/eth2/state_processing/src/test_utils.rs index f18a8e70b..20e792343 100644 --- a/eth2/state_processing/src/test_utils.rs +++ b/eth2/state_processing/src/test_utils.rs @@ -1,10 +1,12 @@ use log::info; use types::test_utils::{ - AttestationTestTask, AttesterSlashingTestTask, DepositTestTask, ExitTestTask, - ProposerSlashingTestTask, TestingBeaconBlockBuilder, TestingBeaconStateBuilder, + AttestationTestTask, AttesterSlashingTestTask, DepositTestTask, ProposerSlashingTestTask, + TestingBeaconBlockBuilder, TestingBeaconStateBuilder, }; use types::{EthSpec, *}; +pub use crate::per_block_processing::block_processing_builder::BlockProcessingBuilder; + pub struct BlockBuilder { pub state_builder: TestingBeaconStateBuilder, pub block_builder: TestingBeaconBlockBuilder, @@ -155,10 +157,10 @@ impl BlockBuilder { let validator_index = validators_iter.next().expect("Insufficient validators."); builder.insert_exit( - ExitTestTask::Valid, - &mut state, validator_index, + state.current_epoch(), &keypairs[validator_index as usize].sk, + &state, spec, ); } diff --git a/eth2/types/src/slot_epoch.rs b/eth2/types/src/slot_epoch.rs index 86de83258..42b922cfb 100644 --- a/eth2/types/src/slot_epoch.rs +++ b/eth2/types/src/slot_epoch.rs @@ -35,7 +35,7 @@ impl_common!(Slot); impl_common!(Epoch); impl Slot { - pub fn new(slot: u64) -> Slot { + pub const fn new(slot: u64) -> Slot { Slot(slot) } @@ -49,7 +49,7 @@ impl Slot { } impl Epoch { - pub fn new(slot: u64) -> Epoch { + pub const fn new(slot: u64) -> Epoch { Epoch(slot) } diff --git a/eth2/types/src/test_utils/builders/testing_attestation_data_builder.rs b/eth2/types/src/test_utils/builders/testing_attestation_data_builder.rs index 68879da37..60663cea6 100644 --- a/eth2/types/src/test_utils/builders/testing_attestation_data_builder.rs +++ b/eth2/types/src/test_utils/builders/testing_attestation_data_builder.rs @@ -48,9 +48,6 @@ impl TestingAttestationDataBuilder { let beacon_block_root = *state.get_block_root(slot).unwrap(); match test_task { - // FIXME: re-enable the shard-like tests - // AttestationTestTask::NoCommiteeForShard => index += 2, - // AttestationTestTask::BadShard => index = T::ShardCount::to_u64(), AttestationTestTask::IncludedTooEarly => { slot = state.slot - spec.min_attestation_inclusion_delay + 1 } diff --git a/eth2/types/src/test_utils/builders/testing_beacon_block_builder.rs b/eth2/types/src/test_utils/builders/testing_beacon_block_builder.rs index b0d0640af..ef98ee834 100644 --- a/eth2/types/src/test_utils/builders/testing_beacon_block_builder.rs +++ b/eth2/types/src/test_utils/builders/testing_beacon_block_builder.rs @@ -28,25 +28,11 @@ pub enum DepositTestTask { NoReset, } -/// Enum used for passing test options to builder -#[derive(PartialEq, Clone, Copy)] -pub enum ExitTestTask { - AlreadyInitiated, - AlreadyExited, - BadSignature, - FutureEpoch, - NotActive, - Valid, - ValidatorUnknown, -} - /// Enum used for passing test options to builder #[derive(PartialEq, Clone, Copy)] pub enum AttestationTestTask { Valid, - NoCommiteeForShard, WrongJustifiedCheckpoint, - BadShard, BadIndexedAttestationBadSignature, BadAggregationBitfieldLen, BadSignature, @@ -348,39 +334,25 @@ impl TestingBeaconBlockBuilder { } } - /// Insert a `Valid` exit into the state. + /// Insert an exit for the given validator at the given epoch into the block. pub fn insert_exit( &mut self, - test_task: ExitTestTask, - state: &mut BeaconState, - mut validator_index: u64, + validator_index: u64, + exit_epoch: Epoch, secret_key: &SecretKey, + state: &BeaconState, spec: &ChainSpec, ) { - let sk = &mut secret_key.clone(); - let mut exit_epoch = state.slot.epoch(T::slots_per_epoch()); - - match test_task { - ExitTestTask::BadSignature => *sk = SecretKey::random(), - ExitTestTask::ValidatorUnknown => validator_index = 4242, - ExitTestTask::AlreadyExited => { - state.validators[validator_index as usize].exit_epoch = Epoch::from(314_159 as u64) - } - // FIXME: disabled in v0.9 - ExitTestTask::NotActive => { - state.validators[validator_index as usize].activation_epoch = - Epoch::from(314_159 as u64) - } - ExitTestTask::FutureEpoch => exit_epoch = spec.far_future_epoch, - _ => (), - } - let builder = TestingVoluntaryExitBuilder::new(exit_epoch, validator_index); - let exit = builder.build(sk, &state.fork, state.genesis_validators_root, spec); - + let exit = builder.build(secret_key, &state.fork, state.genesis_validators_root, spec); self.block.body.voluntary_exits.push(exit).unwrap(); } + /// Mutate the block before signing. + pub fn modify(&mut self, f: impl FnOnce(&mut BeaconBlock)) { + f(&mut self.block) + } + /// Signs and returns the block, consuming the builder. pub fn build( self, diff --git a/eth2/types/src/test_utils/builders/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/builders/testing_beacon_state_builder.rs index c4580a48f..84b8067a2 100644 --- a/eth2/types/src/test_utils/builders/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/builders/testing_beacon_state_builder.rs @@ -164,9 +164,10 @@ impl TestingBeaconStateBuilder { } /// Sets the `BeaconState` to be in a slot, calling `teleport_to_epoch` to update the epoch. - pub fn teleport_to_slot(&mut self, slot: Slot) { + pub fn teleport_to_slot(&mut self, slot: Slot) -> &mut Self { self.teleport_to_epoch(slot.epoch(T::slots_per_epoch())); self.state.slot = slot; + self } /// Sets the `BeaconState` to be in the first slot of the given epoch. diff --git a/eth2/utils/bls/src/signature_set.rs b/eth2/utils/bls/src/signature_set.rs index 76a82e96d..aa0a3839b 100644 --- a/eth2/utils/bls/src/signature_set.rs +++ b/eth2/utils/bls/src/signature_set.rs @@ -52,6 +52,7 @@ where { } } +#[cfg(not(feature = "fake_crypto"))] type VerifySet<'a> = ( &'a RawAggregateSignature, &'a RawAggregatePublicKey, @@ -69,6 +70,6 @@ pub fn verify_signature_sets<'a>(sets: Vec) -> bool { } #[cfg(feature = "fake_crypto")] -pub fn verify_signature_sets<'a>(sets: Vec) -> bool { +pub fn verify_signature_sets<'a>(_: Vec) -> bool { true } diff --git a/tests/state_transition_vectors/.gitignore b/tests/state_transition_vectors/.gitignore new file mode 100644 index 000000000..e5f71b8b7 --- /dev/null +++ b/tests/state_transition_vectors/.gitignore @@ -0,0 +1 @@ +/vectors/ diff --git a/tests/state_transition_vectors/Cargo.toml b/tests/state_transition_vectors/Cargo.toml new file mode 100644 index 000000000..b56bad5e8 --- /dev/null +++ b/tests/state_transition_vectors/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "state_transition_vectors" +version = "0.1.0" +authors = ["Paul Hauner "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +state_processing = { path = "../../eth2/state_processing" } +types = { path = "../../eth2/types" } +eth2_ssz = "0.1.2" diff --git a/tests/state_transition_vectors/Makefile b/tests/state_transition_vectors/Makefile new file mode 100644 index 000000000..e06c71918 --- /dev/null +++ b/tests/state_transition_vectors/Makefile @@ -0,0 +1,8 @@ +produce-vectors: + cargo run --release + +test: + cargo test --release + +clean: + rm -r vectors/ diff --git a/tests/state_transition_vectors/README.md b/tests/state_transition_vectors/README.md new file mode 100644 index 000000000..1077b18aa --- /dev/null +++ b/tests/state_transition_vectors/README.md @@ -0,0 +1,72 @@ +# state_transition_vectors + +This crate contains test vectors for Lighthouse state transition functions. + +This crate serves two purposes: + +- Outputting the test vectors to disk via `make`. +- Running the vectors against our code via `make test`. + + +## Outputting vectors to disk + +Whilst we don't actually need to write the vectors to disk to test them, we +provide this functionality so we can generate corpra for the fuzzer and also so +they can be of use to other clients. + +To create the files in `./vectors` (directory relative to this crate), run: + +```bash +make +``` + +This will produce a directory structure that looks roughly like this: + +``` +vectors +└── exit + ├── invalid_bad_signature + │   ├── block.ssz + │   ├── error.txt + │   └── pre.ssz + ├── invalid_duplicate + │   ├── block.ssz + │   ├── error.txt + │   └── pre.ssz + ├── invalid_exit_already_initiated + │   ├── block.ssz + │   ├── error.txt + │   └── pre.ssz + ├── invalid_future_exit_epoch + │   ├── block.ssz + │   ├── error.txt + │   └── pre.ssz + ├── invalid_not_active_after_exit_epoch + │   ├── block.ssz + │   ├── error.txt + │   └── pre.ssz + ├── invalid_not_active_before_activation_epoch + │   ├── block.ssz + │   ├── error.txt + │   └── pre.ssz + ├── invalid_too_young_by_a_lot + │   ├── block.ssz + │   ├── error.txt + │   └── pre.ssz + ├── invalid_too_young_by_one_epoch + │   ├── block.ssz + │   ├── error.txt + │   └── pre.ssz + ├── invalid_validator_unknown + │   ├── block.ssz + │   ├── error.txt + │   └── pre.ssz + ├── valid_genesis_epoch + │   ├── block.ssz + │   ├── post.ssz + │   └── pre.ssz + └── valid_previous_epoch + ├── block.ssz + ├── post.ssz + └── pre.ssz +``` diff --git a/tests/state_transition_vectors/src/exit.rs b/tests/state_transition_vectors/src/exit.rs new file mode 100644 index 000000000..3f156d29e --- /dev/null +++ b/tests/state_transition_vectors/src/exit.rs @@ -0,0 +1,346 @@ +use super::*; +use state_processing::{ + per_block_processing, per_block_processing::errors::ExitInvalid, + test_utils::BlockProcessingBuilder, BlockProcessingError, BlockSignatureStrategy, +}; +use types::{BeaconBlock, BeaconState, Epoch, EthSpec, SignedBeaconBlock}; + +// Default validator index to exit. +pub const VALIDATOR_INDEX: u64 = 0; +// Epoch that the state will be transitioned to by default, equal to PERSISTENT_COMMITTEE_PERIOD. +pub const STATE_EPOCH: Epoch = Epoch::new(2048); + +struct ExitTest { + validator_index: u64, + exit_epoch: Epoch, + state_epoch: Epoch, + block_modifier: Box)>, + builder_modifier: Box) -> BlockProcessingBuilder>, + #[allow(dead_code)] + expected: Result<(), BlockProcessingError>, +} + +impl Default for ExitTest { + fn default() -> Self { + Self { + validator_index: VALIDATOR_INDEX, + exit_epoch: STATE_EPOCH, + state_epoch: STATE_EPOCH, + block_modifier: Box::new(|_| ()), + builder_modifier: Box::new(|x| x), + expected: Ok(()), + } + } +} + +impl ExitTest { + fn block_and_pre_state(self) -> (SignedBeaconBlock, BeaconState) { + let spec = &E::default_spec(); + + (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) + } + + fn process( + block: &SignedBeaconBlock, + state: &mut BeaconState, + ) -> Result<(), BlockProcessingError> { + per_block_processing( + state, + block, + None, + BlockSignatureStrategy::VerifyIndividual, + &E::default_spec(), + ) + } + + #[cfg(test)] + fn run(self) -> BeaconState { + let spec = &E::default_spec(); + let expected = self.expected.clone(); + assert_eq!(STATE_EPOCH, spec.persistent_committee_period); + + let (block, mut state) = self.block_and_pre_state(); + + let result = Self::process(&block, &mut state); + + assert_eq!(result, expected); + + state + } + + fn test_vector(self, title: String) -> TestVector { + let (block, pre_state) = self.block_and_pre_state(); + let mut post_state = pre_state.clone(); + let (post_state, error) = match Self::process(&block, &mut post_state) { + Ok(_) => (Some(post_state), None), + Err(e) => (None, Some(format!("{:?}", e))), + }; + + TestVector { + title, + block, + pre_state, + post_state, + error, + } + } +} + +vectors_and_tests!( + // Ensures we can process a valid exit, + valid_single_exit, + ExitTest::default(), + // Tests three exists in the same block. + valid_three_exits, + ExitTest { + builder_modifier: Box::new(|builder| { + builder + .insert_exit(1, STATE_EPOCH) + .insert_exit(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| { + // Duplicate the exit + let exit = block.body.voluntary_exits[0].clone(); + block.body.voluntary_exits.push(exit).unwrap(); + }), + expected: Err(BlockProcessingError::ExitInvalid { + index: 1, + reason: ExitInvalid::AlreadyExited(0), + }), + ..ExitTest::default() + }, + // Tests the following line of the spec: + // + // v0.11.2 + // + // ```ignore + // validator = state.validators[voluntary_exit.validator_index] + // ``` + invalid_validator_unknown, + ExitTest { + block_modifier: Box::new(|block| { + block.body.voluntary_exits[0].message.validator_index = VALIDATOR_COUNT as u64; + }), + expected: Err(BlockProcessingError::ExitInvalid { + index: 0, + reason: ExitInvalid::ValidatorUnknown(VALIDATOR_COUNT as u64), + }), + ..ExitTest::default() + }, + // Tests the following line of the spec: + // + // v0.11.2 + // + // ```ignore + // # Verify exit has not been initiated + // assert validator.exit_epoch == FAR_FUTURE_EPOCH + // ``` + invalid_exit_already_initiated, + ExitTest { + builder_modifier: Box::new(|mut builder| { + builder.state.validators[0].exit_epoch = STATE_EPOCH + 1; + builder + }), + expected: Err(BlockProcessingError::ExitInvalid { + index: 0, + reason: ExitInvalid::AlreadyExited(0), + }), + ..ExitTest::default() + }, + // Tests the following line of the spec: + // + // v0.11.2 + // + // ```ignore + // # Verify the validator is active + // assert is_active_validator(validator, get_current_epoch(state)) + // ``` + 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 + }), + expected: Err(BlockProcessingError::ExitInvalid { + index: 0, + reason: ExitInvalid::NotActive(0), + }), + ..ExitTest::default() + }, + // Also tests the following line of the spec: + // + // v0.11.2 + // + // ```ignore + // # Verify the validator is active + // assert is_active_validator(validator, get_current_epoch(state)) + // ``` + invalid_not_active_after_exit_epoch, + ExitTest { + builder_modifier: Box::new(|mut builder| { + builder.state.validators[0].exit_epoch = STATE_EPOCH; + builder + }), + expected: Err(BlockProcessingError::ExitInvalid { + index: 0, + reason: ExitInvalid::NotActive(0), + }), + ..ExitTest::default() + }, + // Ensures we can process an exit from genesis. + valid_genesis_epoch, + ExitTest { + exit_epoch: Epoch::new(0), + ..ExitTest::default() + }, + // Ensures we can process an exit from the previous epoch. + valid_previous_epoch, + ExitTest { + exit_epoch: STATE_EPOCH - 1, + ..ExitTest::default() + }, + // Tests the following line of the spec: + // + // v0.11.2 + // + // ```ignore + // # Exits must specify an epoch when they become valid; they are not + // # valid before then + // assert get_current_epoch(state) >= voluntary_exit.epoch + // ``` + invalid_future_exit_epoch, + ExitTest { + exit_epoch: STATE_EPOCH + 1, + expected: Err(BlockProcessingError::ExitInvalid { + index: 0, + reason: ExitInvalid::FutureEpoch { + state: STATE_EPOCH, + exit: STATE_EPOCH + 1, + }, + }), + ..ExitTest::default() + }, + // Tests the following line of the spec: + // + // v0.11.2 + // + // ```ignore + // # Verify the validator has been active long enough + // assert get_current_epoch(state) >= validator.activation_epoch + PERSISTENT_COMMITTEE_PERIOD + // ``` + invalid_too_young_by_one_epoch, + ExitTest { + state_epoch: STATE_EPOCH - 1, + exit_epoch: STATE_EPOCH - 1, + expected: Err(BlockProcessingError::ExitInvalid { + index: 0, + reason: ExitInvalid::TooYoungToExit { + current_epoch: STATE_EPOCH - 1, + earliest_exit_epoch: STATE_EPOCH, + }, + }), + ..ExitTest::default() + }, + // Also tests the following line of the spec: + // + // v0.11.2 + // + // ```ignore + // # Verify the validator has been active long enough + // assert get_current_epoch(state) >= validator.activation_epoch + PERSISTENT_COMMITTEE_PERIOD + // ``` + invalid_too_young_by_a_lot, + ExitTest { + state_epoch: Epoch::new(0), + exit_epoch: Epoch::new(0), + expected: Err(BlockProcessingError::ExitInvalid { + index: 0, + reason: ExitInvalid::TooYoungToExit { + current_epoch: Epoch::new(0), + earliest_exit_epoch: STATE_EPOCH, + }, + }), + ..ExitTest::default() + }, + // Tests the following line of the spec: + // + // v0.11.2 + // + // ```ignore + // # Verify signature + // domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, + // voluntary_exit.epoch) + // signing_root = compute_signing_root(voluntary_exit, domain) + // assert bls.Verify(validator.pubkey, signing_root, + // signed_voluntary_exit.signature) + // ``` + invalid_bad_signature, + ExitTest { + 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; + }), + expected: Err(BlockProcessingError::ExitInvalid { + index: 0, + reason: ExitInvalid::BadSignature, + }), + ..ExitTest::default() + } +); + +#[cfg(test)] +mod custom_tests { + use super::*; + + fn assert_exited(state: &BeaconState, validator_index: usize) { + let spec = E::default_spec(); + + let validator = &state.validators[validator_index]; + assert_eq!( + validator.exit_epoch, + // This is correct until we exceed the churn limit. If that happens, we + // need to introduce more complex logic. + state.current_epoch() + 1 + spec.max_seed_lookahead, + "exit epoch" + ); + assert_eq!( + validator.withdrawable_epoch, + validator.exit_epoch + E::default_spec().min_validator_withdrawability_delay, + "withdrawable epoch" + ); + } + + #[test] + fn valid() { + let state = ExitTest::default().run(); + assert_exited(&state, VALIDATOR_INDEX as usize); + } + + #[test] + fn valid_three() { + let state = ExitTest { + builder_modifier: Box::new(|builder| { + builder + .insert_exit(1, STATE_EPOCH) + .insert_exit(2, STATE_EPOCH) + }), + ..ExitTest::default() + } + .run(); + + for i in &[VALIDATOR_INDEX, 1, 2] { + assert_exited(&state, *i as usize); + } + } +} diff --git a/tests/state_transition_vectors/src/macros.rs b/tests/state_transition_vectors/src/macros.rs new file mode 100644 index 000000000..ff70be40e --- /dev/null +++ b/tests/state_transition_vectors/src/macros.rs @@ -0,0 +1,28 @@ +/// Provides: +/// +/// - `fn vectors()`: allows for getting a `Vec` of all vectors for exporting. +/// - `mod tests`: runs all the test vectors locally. +macro_rules! vectors_and_tests { + ($($name: ident, $test: expr),*) => { + pub fn vectors() -> Vec { + let mut vec = vec![]; + + $( + vec.push($test.test_vector(stringify!($name).into())); + )* + + vec + } + + #[cfg(test)] + mod tests { + use super::*; + $( + #[test] + fn $name() { + $test.run(); + } + )* + } + }; +} diff --git a/tests/state_transition_vectors/src/main.rs b/tests/state_transition_vectors/src/main.rs new file mode 100644 index 000000000..e259399a7 --- /dev/null +++ b/tests/state_transition_vectors/src/main.rs @@ -0,0 +1,107 @@ +#[macro_use] +mod macros; +mod exit; + +use ssz::Encode; +use state_processing::test_utils::BlockProcessingBuilder; +use std::env; +use std::fs::{self, File}; +use std::io::Write; +use std::path::PathBuf; +use std::process::exit; +use types::MainnetEthSpec; +use types::{BeaconState, ChainSpec, EthSpec, SignedBeaconBlock}; + +type E = MainnetEthSpec; + +pub const VALIDATOR_COUNT: usize = 64; + +/// The base output directory for test vectors. +pub const BASE_VECTOR_DIR: &str = "vectors"; + +/// Writes all known test vectors to `CARGO_MANIFEST_DIR/vectors`. +fn main() { + match write_all_vectors() { + Ok(()) => exit(0), + Err(e) => { + eprintln!("Error: {}", e); + exit(1) + } + } +} + +/// An abstract definition of a test vector that can be run as a test or exported to disk. +pub struct TestVector { + pub title: String, + pub pre_state: BeaconState, + pub block: SignedBeaconBlock, + pub post_state: Option>, + pub error: Option, +} + +/// Gets a `BlockProcessingBuilder` to be used in testing. +fn get_builder( + spec: &ChainSpec, + epoch_offset: u64, + num_validators: usize, +) -> BlockProcessingBuilder { + // 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() +} + +/// Writes all vectors to file. +fn write_all_vectors() -> Result<(), String> { + write_vectors_to_file("exit", &exit::vectors()) +} + +/// Writes a list of `vectors` to the `title` dir. +fn write_vectors_to_file(title: &str, vectors: &[TestVector]) -> Result<(), String> { + let dir = env::var("CARGO_MANIFEST_DIR") + .map_err(|e| format!("Unable to find manifest dir: {:?}", e))? + .parse::() + .map_err(|e| format!("Unable to parse manifest dir: {:?}", e))? + .join(BASE_VECTOR_DIR) + .join(title); + + if dir.exists() { + fs::remove_dir_all(&dir).map_err(|e| format!("Unable to remove {:?}: {:?}", dir, e))?; + } + fs::create_dir_all(&dir).map_err(|e| format!("Unable to create {:?}: {:?}", dir, e))?; + + for vector in vectors { + let dir = dir.clone().join(&vector.title); + if dir.exists() { + fs::remove_dir_all(&dir).map_err(|e| format!("Unable to remove {:?}: {:?}", dir, e))?; + } + fs::create_dir_all(&dir).map_err(|e| format!("Unable to create {:?}: {:?}", dir, e))?; + + write_to_ssz_file(&dir.clone().join("pre.ssz"), &vector.pre_state)?; + write_to_ssz_file(&dir.clone().join("block.ssz"), &vector.block)?; + if let Some(post_state) = vector.post_state.as_ref() { + write_to_ssz_file(&dir.clone().join("post.ssz"), post_state)?; + } + if let Some(error) = vector.error.as_ref() { + write_to_file(&dir.clone().join("error.txt"), error.as_bytes())?; + } + } + + Ok(()) +} + +/// Write some SSZ object to file. +fn write_to_ssz_file(path: &PathBuf, item: &T) -> Result<(), String> { + write_to_file(path, &item.as_ssz_bytes()) +} + +/// Write some bytes to file. +fn write_to_file(path: &PathBuf, item: &[u8]) -> Result<(), String> { + File::create(path) + .map_err(|e| format!("Unable to create {:?}: {:?}", path, e)) + .and_then(|mut file| { + file.write_all(item) + .map(|_| ()) + .map_err(|e| format!("Unable to write to {:?}: {:?}", path, e)) + }) +}