Fix parallelism bug in exit processing (#1110)

* Fix parallelism bug in exit processing

Also:

* Remove parallelism for all other operations except deposit merkle proofs
* Improve exit tests
* Fix broken attestation test

Closes #1090

* Allow for generating block/pre/post states from some unit tests (#1123)

* Add post-state checks, comments

* Add state_transition_vectors crate

* Integrate new testing crate with CI

* Add readme

* Add additional valid tests

* Remove ExitTests (they were moved to new crate)

* Small test fixes

* Delete incorrect saturating_sub in slash_validator

And clean-up the balance increase/decrease functions to look more like the spec.

Co-authored-by: Paul Hauner <paul@paulhauner.com>
This commit is contained in:
Michael Sproul 2020-05-09 09:37:21 +10:00 committed by GitHub
parent ad5bd6412a
commit 338cb2fba7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 846 additions and 562 deletions

View File

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

9
Cargo.lock generated
View File

@ -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"

View File

@ -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",

View File

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

View File

@ -1,4 +1,4 @@
// #![cfg(not(debug_assertions))]
#![cfg(not(debug_assertions))]
#[macro_use]
extern crate lazy_static;

View File

@ -218,7 +218,7 @@ impl<T: EthSpec> OperationPool<T> {
state: &BeaconState<T>,
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(())

View File

@ -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<E: EthSpec>(
state: &mut BeaconState<E>,
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<E: EthSpec>(state: &mut BeaconState<E>, index: usize, delta: u64) {
state.balances[index] = state.balances[index].saturating_sub(delta);
}

View File

@ -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<T: EthSpec>(
.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<T: EthSpec>(
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(())
}

View File

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

View File

@ -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<T: EthSpec>(
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<T: EthSpec>(
// 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<T: EthSpec>(
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<T: EthSpec>(
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(())
}

View File

@ -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<T: EthSpec> {
pub state_builder: TestingBeaconStateBuilder<T>,
pub struct BlockProcessingBuilder<'a, T: EthSpec> {
pub state: BeaconState<T>,
pub keypairs: Vec<Keypair>,
pub block_builder: TestingBeaconBlockBuilder<T>,
pub num_validators: usize,
pub spec: &'a ChainSpec,
}
impl<T: EthSpec> BlockProcessingBuilder<T> {
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<T: EthSpec> BlockProcessingBuilder<T> {
previous_block_root: Option<Hash256>,
spec: &ChainSpec,
) -> (SignedBeaconBlock<T>, BeaconState<T>) {
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<T: EthSpec> BlockProcessingBuilder<T> {
(block, state)
}
pub fn build_with_n_exits(
mut self,
num_exits: usize,
test_task: ExitTestTask,
randao_sk: Option<SecretKey>,
previous_block_root: Option<Hash256>,
spec: &ChainSpec,
) -> (SignedBeaconBlock<T>, BeaconState<T>) {
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<T>)) -> Self {
self.block_builder.modify(f);
self
}
pub fn build_with_n_attestations(
@ -166,7 +167,7 @@ impl<T: EthSpec> BlockProcessingBuilder<T> {
previous_block_root: Option<Hash256>,
spec: &ChainSpec,
) -> (SignedBeaconBlock<T>, BeaconState<T>) {
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<T: EthSpec> BlockProcessingBuilder<T> {
previous_block_root: Option<Hash256>,
spec: &ChainSpec,
) -> (SignedBeaconBlock<T>, BeaconState<T>) {
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<T: EthSpec> BlockProcessingBuilder<T> {
previous_block_root: Option<Hash256>,
spec: &ChainSpec,
) -> (SignedBeaconBlock<T>, BeaconState<T>) {
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<T: EthSpec> BlockProcessingBuilder<T> {
(block, state)
}
// NOTE: could remove optional args
// NOTE: could return keypairs as well
pub fn build(
mut self,
randao_sk: Option<SecretKey>,
previous_block_root: Option<Hash256>,
spec: &ChainSpec,
) -> (SignedBeaconBlock<T>, BeaconState<T>) {
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);

View File

@ -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<MainnetEthSpec> {
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()
}

View File

@ -10,16 +10,16 @@ fn error(reason: Invalid) -> BlockOperationError<Invalid> {
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<T: EthSpec>(
state: &BeaconState<T>,
attester_slashing: &AttesterSlashing<T>,
should_verify_indexed_attestations: bool,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<()> {
@ -33,12 +33,10 @@ pub fn verify_attester_slashing<T: EthSpec>(
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(())
}

View File

@ -26,7 +26,8 @@ pub fn process_slashings<T: EthSpec>(
.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);
}
}

View File

@ -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<T: EthSpec> {
pub state_builder: TestingBeaconStateBuilder<T>,
pub block_builder: TestingBeaconBlockBuilder<T>,
@ -155,10 +157,10 @@ impl<T: EthSpec> BlockBuilder<T> {
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,
);
}

View File

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

View File

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

View File

@ -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<T: EthSpec> TestingBeaconBlockBuilder<T> {
}
}
/// 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<T>,
mut validator_index: u64,
validator_index: u64,
exit_epoch: Epoch,
secret_key: &SecretKey,
state: &BeaconState<T>,
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<T>)) {
f(&mut self.block)
}
/// Signs and returns the block, consuming the builder.
pub fn build(
self,

View File

@ -164,9 +164,10 @@ impl<T: EthSpec> TestingBeaconStateBuilder<T> {
}
/// 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.

View File

@ -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<SignatureSet>) -> bool {
}
#[cfg(feature = "fake_crypto")]
pub fn verify_signature_sets<'a>(sets: Vec<SignatureSet>) -> bool {
pub fn verify_signature_sets<'a>(_: Vec<SignatureSet>) -> bool {
true
}

View File

@ -0,0 +1 @@
/vectors/

View File

@ -0,0 +1,12 @@
[package]
name = "state_transition_vectors"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
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"

View File

@ -0,0 +1,8 @@
produce-vectors:
cargo run --release
test:
cargo test --release
clean:
rm -r vectors/

View File

@ -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
```

View File

@ -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<dyn FnOnce(&mut BeaconBlock<E>)>,
builder_modifier: Box<dyn FnOnce(BlockProcessingBuilder<E>) -> BlockProcessingBuilder<E>>,
#[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<E>, BeaconState<E>) {
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<E>,
state: &mut BeaconState<E>,
) -> Result<(), BlockProcessingError> {
per_block_processing(
state,
block,
None,
BlockSignatureStrategy::VerifyIndividual,
&E::default_spec(),
)
}
#[cfg(test)]
fn run(self) -> BeaconState<E> {
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<E>, 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);
}
}
}

View File

@ -0,0 +1,28 @@
/// Provides:
///
/// - `fn vectors()`: allows for getting a `Vec<TestVector>` 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<TestVector> {
let mut vec = vec![];
$(
vec.push($test.test_vector(stringify!($name).into()));
)*
vec
}
#[cfg(test)]
mod tests {
use super::*;
$(
#[test]
fn $name() {
$test.run();
}
)*
}
};
}

View File

@ -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<E>,
pub block: SignedBeaconBlock<E>,
pub post_state: Option<BeaconState<E>>,
pub error: Option<String>,
}
/// Gets a `BlockProcessingBuilder` to be used in testing.
fn get_builder(
spec: &ChainSpec,
epoch_offset: u64,
num_validators: usize,
) -> BlockProcessingBuilder<MainnetEthSpec> {
// Set the state and block to be in the last slot of the `epoch_offset`th epoch.
let last_slot_of_epoch = (MainnetEthSpec::genesis_epoch() + epoch_offset)
.end_slot(MainnetEthSpec::slots_per_epoch());
BlockProcessingBuilder::new(num_validators, last_slot_of_epoch, &spec).build_caches()
}
/// 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::<PathBuf>()
.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<T: Encode>(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))
})
}