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:
parent
ad5bd6412a
commit
338cb2fba7
9
.github/workflows/test-suite.yml
vendored
9
.github/workflows/test-suite.yml
vendored
@ -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
9
Cargo.lock
generated
@ -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"
|
||||
|
@ -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",
|
||||
|
6
Makefile
6
Makefile
@ -1,6 +1,7 @@
|
||||
.PHONY: tests
|
||||
|
||||
EF_TESTS = "tests/ef_tests"
|
||||
STATE_TRANSITION_VECTORS = "tests/state_transition_vectors"
|
||||
|
||||
# Builds the Lighthouse binary in release (optimized).
|
||||
#
|
||||
@ -35,6 +36,10 @@ run-ef-tests:
|
||||
cargo test --release --manifest-path=$(EF_TESTS)/Cargo.toml --features "ef_tests"
|
||||
cargo test --release --manifest-path=$(EF_TESTS)/Cargo.toml --features "ef_tests,fake_crypto"
|
||||
|
||||
# Runs only the tests/state_transition_vectors tests.
|
||||
run-state-transition-tests:
|
||||
make -C $(STATE_TRANSITION_VECTORS) test
|
||||
|
||||
# Downloads and runs the EF test vectors.
|
||||
test-ef: make-ef-tests run-ef-tests
|
||||
|
||||
@ -66,3 +71,4 @@ arbitrary-fuzz:
|
||||
clean:
|
||||
cargo clean
|
||||
make -C $(EF_TESTS) clean
|
||||
make -C $(STATE_TRANSITION_VECTORS) clean
|
||||
|
@ -1,4 +1,4 @@
|
||||
// #![cfg(not(debug_assertions))]
|
||||
#![cfg(not(debug_assertions))]
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
@ -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(())
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
1
tests/state_transition_vectors/.gitignore
vendored
Normal file
1
tests/state_transition_vectors/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/vectors/
|
12
tests/state_transition_vectors/Cargo.toml
Normal file
12
tests/state_transition_vectors/Cargo.toml
Normal 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"
|
8
tests/state_transition_vectors/Makefile
Normal file
8
tests/state_transition_vectors/Makefile
Normal file
@ -0,0 +1,8 @@
|
||||
produce-vectors:
|
||||
cargo run --release
|
||||
|
||||
test:
|
||||
cargo test --release
|
||||
|
||||
clean:
|
||||
rm -r vectors/
|
72
tests/state_transition_vectors/README.md
Normal file
72
tests/state_transition_vectors/README.md
Normal 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
|
||||
```
|
346
tests/state_transition_vectors/src/exit.rs
Normal file
346
tests/state_transition_vectors/src/exit.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
28
tests/state_transition_vectors/src/macros.rs
Normal file
28
tests/state_transition_vectors/src/macros.rs
Normal 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();
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
107
tests/state_transition_vectors/src/main.rs
Normal file
107
tests/state_transition_vectors/src/main.rs
Normal 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))
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user