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
27 changed files with 846 additions and 562 deletions

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