Directory Restructure (#1163)

* Move tests -> testing

* Directory restructure

* Update Cargo.toml during restructure

* Update Makefile during restructure

* Fix arbitrary path
This commit is contained in:
Paul Hauner
2020-05-18 21:24:23 +10:00
committed by GitHub
parent c571afb8d8
commit 4331834003
358 changed files with 217 additions and 229 deletions

2
consensus/state_processing/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
flame.sh
*.svg

View File

@@ -0,0 +1,48 @@
[package]
name = "state_processing"
version = "0.2.0"
authors = ["Paul Hauner <paul@paulhauner.com>", "Michael Sproul <michael@sigmaprime.io>"]
edition = "2018"
[[bench]]
name = "benches"
harness = false
[dev-dependencies]
criterion = "0.3.2"
env_logger = "0.7.1"
serde = "1.0.110"
serde_derive = "1.0.110"
lazy_static = "1.4.0"
serde_yaml = "0.8.11"
beacon_chain = { path = "../../beacon_node/beacon_chain" }
store = { path = "../../beacon_node/store" }
[dependencies]
bls = { path = "../../crypto/bls" }
integer-sqrt = "0.1.3"
itertools = "0.9.0"
eth2_ssz = "0.1.2"
eth2_ssz_types = { path = "../ssz_types" }
merkle_proof = { path = "../merkle_proof" }
log = "0.4.8"
safe_arith = { path = "../safe_arith" }
tree_hash = "0.1.0"
tree_hash_derive = "0.2.0"
types = { path = "../types" }
rayon = "1.3.0"
eth2_hashing = "0.1.0"
int_to_bytes = { path = "../int_to_bytes" }
arbitrary = { version = "0.4.4", features = ["derive"], optional = true }
[features]
fake_crypto = ["bls/fake_crypto"]
arbitrary-fuzz = [
"arbitrary",
"types/arbitrary-fuzz",
"bls/arbitrary",
"merkle_proof/arbitrary",
"eth2_ssz/arbitrary",
"eth2_ssz_types/arbitrary",
"tree_hash/arbitrary",
]

View File

@@ -0,0 +1,429 @@
extern crate env_logger;
use criterion::Criterion;
use criterion::{black_box, criterion_group, criterion_main, Benchmark};
use ssz::Encode;
use state_processing::{test_utils::BlockBuilder, BlockSignatureStrategy, VerifySignatures};
use types::{
BeaconState, ChainSpec, EthSpec, MainnetEthSpec, MinimalEthSpec, SignedBeaconBlock, Slot,
};
pub const VALIDATORS_LOW: usize = 32_768;
pub const VALIDATORS_HIGH: usize = 300_032;
fn all_benches(c: &mut Criterion) {
env_logger::init();
average_bench::<MinimalEthSpec>(c, "minimal", VALIDATORS_LOW);
average_bench::<MainnetEthSpec>(c, "mainnet", VALIDATORS_LOW);
average_bench::<MainnetEthSpec>(c, "mainnet", VALIDATORS_HIGH);
worst_bench::<MinimalEthSpec>(c, "minimal", VALIDATORS_LOW);
worst_bench::<MainnetEthSpec>(c, "mainnet", VALIDATORS_LOW);
worst_bench::<MainnetEthSpec>(c, "mainnet", VALIDATORS_HIGH);
}
/// Run a bench with a average complexity block.
fn average_bench<T: EthSpec>(c: &mut Criterion, spec_desc: &str, validator_count: usize) {
let spec = &T::default_spec();
let (block, state) = get_average_block(validator_count, spec);
bench_block::<T>(c, block, state, spec, spec_desc, "average_complexity_block");
}
/// Run a bench with a highly complex block.
fn worst_bench<T: EthSpec>(c: &mut Criterion, spec_desc: &str, validator_count: usize) {
let mut spec = &mut T::default_spec();
// Allows the exits to be processed sucessfully.
spec.persistent_committee_period = 0;
let (block, state) = get_worst_block(validator_count, spec);
bench_block::<T>(c, block, state, spec, spec_desc, "high_complexity_block");
}
/// Return a block and state where the block has "average" complexity. I.e., the number of
/// operations we'd generally expect to see.
fn get_average_block<T: EthSpec>(
validator_count: usize,
spec: &ChainSpec,
) -> (SignedBeaconBlock<T>, BeaconState<T>) {
let mut builder: BlockBuilder<T> = BlockBuilder::new(validator_count, &spec);
// builder.num_attestations = T::MaxAttestations::to_usize();
builder.num_attestations = 16;
builder.set_slot(Slot::from(T::slots_per_epoch() * 3 - 2));
builder.build_caches(&spec);
builder.build(&spec)
}
/// Return a block and state where the block has the "worst" complexity. The block is not
/// _guaranteed_ to be the worst possible complexity, it just has the max possible operations.
fn get_worst_block<T: EthSpec>(
validator_count: usize,
spec: &ChainSpec,
) -> (SignedBeaconBlock<T>, BeaconState<T>) {
let mut builder: BlockBuilder<T> = BlockBuilder::new(validator_count, &spec);
builder.maximize_block_operations();
// FIXME: enable deposits once we can generate them with valid proofs.
builder.num_deposits = 0;
builder.set_slot(Slot::from(T::slots_per_epoch() * 3 - 2));
builder.build_caches(&spec);
builder.build(&spec)
}
#[allow(clippy::unit_arg)]
fn bench_block<T: EthSpec>(
c: &mut Criterion,
block: SignedBeaconBlock<T>,
state: BeaconState<T>,
spec: &ChainSpec,
spec_desc: &str,
block_desc: &str,
) {
let validator_count = state.validators.len();
let title = &format!(
"{}/{}_validators/{}",
spec_desc, validator_count, block_desc
);
let local_block = block.clone();
let local_state = state.clone();
let local_spec = spec.clone();
c.bench(
&title,
Benchmark::new(
"per_block_processing/individual_signature_verification",
move |b| {
b.iter_batched_ref(
|| (local_spec.clone(), local_state.clone(), local_block.clone()),
|(spec, ref mut state, block)| {
black_box(
state_processing::per_block_processing::<T>(
state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
)
.expect("block processing should succeed"),
)
},
criterion::BatchSize::SmallInput,
)
},
)
.sample_size(10),
);
let local_block = block.clone();
let local_state = state.clone();
let local_spec = spec.clone();
c.bench(
&title,
Benchmark::new(
"per_block_processing/bulk_signature_verification",
move |b| {
b.iter_batched_ref(
|| (local_spec.clone(), local_state.clone(), local_block.clone()),
|(spec, ref mut state, block)| {
black_box(
state_processing::per_block_processing::<T>(
state,
&block,
None,
BlockSignatureStrategy::VerifyBulk,
&spec,
)
.expect("block processing should succeed"),
)
},
criterion::BatchSize::SmallInput,
)
},
)
.sample_size(10),
);
let local_block = block.clone();
let local_state = state.clone();
let local_spec = spec.clone();
c.bench(
&title,
Benchmark::new("per_block_processing/no_signature_verification", move |b| {
b.iter_batched_ref(
|| (local_spec.clone(), local_state.clone(), local_block.clone()),
|(spec, ref mut state, block)| {
black_box(
state_processing::per_block_processing::<T>(
state,
&block,
None,
BlockSignatureStrategy::NoVerification,
&spec,
)
.expect("block processing should succeed"),
)
},
criterion::BatchSize::SmallInput,
)
})
.sample_size(10),
);
let local_block = block.clone();
let local_state = state.clone();
let local_spec = spec.clone();
c.bench(
&title,
Benchmark::new("process_block_header", move |b| {
b.iter_batched_ref(
|| (local_spec.clone(), local_state.clone(), local_block.clone()),
|(spec, ref mut state, block)| {
black_box(
state_processing::per_block_processing::process_block_header::<T>(
state,
&block.message,
&spec,
)
.expect("process_block_header should succeed"),
)
},
criterion::BatchSize::SmallInput,
)
})
.sample_size(10),
);
let local_block = block.clone();
let local_state = state.clone();
let local_spec = spec.clone();
c.bench(
&title,
Benchmark::new("verify_block_signature", move |b| {
b.iter_batched_ref(
|| (local_spec.clone(), local_state.clone(), local_block.clone()),
|(spec, ref mut state, block)| {
black_box(
state_processing::per_block_processing::verify_block_signature::<T>(
state, &block, None, &spec,
)
.expect("verify_block_signature should succeed"),
)
},
criterion::BatchSize::SmallInput,
)
})
.sample_size(10),
);
let local_block = block.clone();
let local_state = state.clone();
let local_spec = spec.clone();
c.bench(
&title,
Benchmark::new("process_attestations", move |b| {
b.iter_batched_ref(
|| (local_spec.clone(), local_state.clone(), local_block.clone()),
|(spec, ref mut state, block)| {
black_box(
state_processing::per_block_processing::process_attestations::<T>(
state,
&block.message.body.attestations,
VerifySignatures::True,
&spec,
)
.expect("attestation processing should succeed"),
)
},
criterion::BatchSize::SmallInput,
)
})
.sample_size(10),
);
let local_block = block.clone();
let local_state = state.clone();
let local_spec = spec.clone();
c.bench(
&title,
Benchmark::new("verify_attestation", move |b| {
b.iter_batched_ref(
|| {
let attestation = &local_block.message.body.attestations[0];
(local_spec.clone(), local_state.clone(), attestation.clone())
},
|(spec, ref mut state, attestation)| {
black_box(
state_processing::per_block_processing::verify_attestation_for_block_inclusion(
state,
&attestation,
VerifySignatures::True,
spec,
)
.expect("should verify attestation"),
)
},
criterion::BatchSize::SmallInput,
)
})
.sample_size(10),
);
let local_block = block.clone();
let local_state = state.clone();
c.bench(
&title,
Benchmark::new("get_indexed_attestation", move |b| {
b.iter_batched_ref(
|| {
let attestation = &local_block.message.body.attestations[0];
let committee = local_state
.get_beacon_committee(attestation.data.slot, attestation.data.index)
.unwrap();
(committee.committee, attestation.clone())
},
|(committee, attestation)| {
black_box(
state_processing::common::get_indexed_attestation(committee, &attestation)
.expect("should get indexed attestation"),
)
},
criterion::BatchSize::SmallInput,
)
})
.sample_size(10),
);
let local_block = block.clone();
let local_state = state.clone();
let local_spec = spec.clone();
c.bench(
&title,
Benchmark::new("is_valid_indexed_attestation_with_signature", move |b| {
b.iter_batched_ref(
|| {
let attestation = &local_block.message.body.attestations[0];
let committee = local_state
.get_beacon_committee(attestation.data.slot, attestation.data.index)
.unwrap();
let indexed_attestation = state_processing::common::get_indexed_attestation(
&committee.committee,
&attestation,
)
.expect("should get indexed attestation");
(local_spec.clone(), local_state.clone(), indexed_attestation)
},
|(spec, ref mut state, indexed_attestation)| {
black_box(
state_processing::per_block_processing::is_valid_indexed_attestation(
state,
&indexed_attestation,
VerifySignatures::True,
spec,
)
.expect("should run is_valid_indexed_attestation"),
)
},
criterion::BatchSize::SmallInput,
)
})
.sample_size(10),
);
let local_block = block.clone();
let local_state = state.clone();
let local_spec = spec.clone();
c.bench(
&title,
Benchmark::new("is_valid_indexed_attestation_without_signature", move |b| {
b.iter_batched_ref(
|| {
let attestation = &local_block.message.body.attestations[0];
let committee = local_state
.get_beacon_committee(attestation.data.slot, attestation.data.index)
.unwrap();
let indexed_attestation = state_processing::common::get_indexed_attestation(
&committee.committee,
&attestation,
)
.expect("should get indexed attestation");
(local_spec.clone(), local_state.clone(), indexed_attestation)
},
|(spec, ref mut state, indexed_attestation)| {
black_box(
state_processing::per_block_processing::is_valid_indexed_attestation(
state,
&indexed_attestation,
VerifySignatures::False,
spec,
)
.expect("should run is_valid_indexed_attestation_without_signature"),
)
},
criterion::BatchSize::SmallInput,
)
})
.sample_size(10),
);
let local_block = block.clone();
let local_state = state;
c.bench(
&title,
Benchmark::new("get_attesting_indices", move |b| {
b.iter_batched_ref(
|| {
let attestation = &local_block.message.body.attestations[0];
let committee = local_state
.get_beacon_committee(attestation.data.slot, attestation.data.index)
.unwrap();
(committee.committee, attestation.clone())
},
|(committee, attestation)| {
black_box(state_processing::common::get_attesting_indices::<T>(
committee,
&attestation.aggregation_bits,
))
},
criterion::BatchSize::SmallInput,
)
})
.sample_size(10),
);
let local_block = block.clone();
c.bench(
&title,
Benchmark::new("ssz_serialize_block", move |b| {
b.iter_batched_ref(
|| (),
|_| black_box(local_block.as_ssz_bytes()),
criterion::BatchSize::SmallInput,
)
})
.sample_size(10),
);
let local_block = block;
c.bench(
&title,
Benchmark::new("ssz_block_len", move |b| {
b.iter_batched_ref(
|| (),
|_| black_box(local_block.ssz_bytes_len()),
criterion::BatchSize::SmallInput,
)
})
.sample_size(10),
);
}
criterion_group!(benches, all_benches,);
criterion_main!(benches);

View File

@@ -0,0 +1,53 @@
use eth2_hashing::hash;
use int_to_bytes::int_to_bytes32;
use merkle_proof::{MerkleTree, MerkleTreeError};
use safe_arith::SafeArith;
use types::Hash256;
/// Emulates the eth1 deposit contract merkle tree.
pub struct DepositDataTree {
tree: MerkleTree,
mix_in_length: usize,
depth: usize,
}
impl DepositDataTree {
/// Create a new Merkle tree from a list of leaves (`DepositData::tree_hash_root`) and a fixed depth.
pub fn create(leaves: &[Hash256], mix_in_length: usize, depth: usize) -> Self {
Self {
tree: MerkleTree::create(leaves, depth),
mix_in_length,
depth,
}
}
/// Returns 32 bytes representing the "mix in length" for the merkle root of this tree.
fn length_bytes(&self) -> Vec<u8> {
int_to_bytes32(self.mix_in_length as u64)
}
/// Retrieve the root hash of this Merkle tree with the length mixed in.
pub fn root(&self) -> Hash256 {
let mut preimage = [0; 64];
preimage[0..32].copy_from_slice(&self.tree.hash()[..]);
preimage[32..64].copy_from_slice(&self.length_bytes());
Hash256::from_slice(&hash(&preimage))
}
/// Return the leaf at `index` and a Merkle proof of its inclusion.
///
/// The Merkle proof is in "bottom-up" order, starting with a leaf node
/// and moving up the tree. Its length will be exactly equal to `depth + 1`.
pub fn generate_proof(&self, index: usize) -> (Hash256, Vec<Hash256>) {
let (root, mut proof) = self.tree.generate_proof(index, self.depth);
proof.push(Hash256::from_slice(&self.length_bytes()));
(root, proof)
}
/// Add a deposit to the merkle tree.
pub fn push_leaf(&mut self, leaf: Hash256) -> Result<(), MerkleTreeError> {
self.tree.push_leaf(leaf, self.depth)?;
self.mix_in_length.increment()?;
Ok(())
}
}

View File

@@ -0,0 +1,25 @@
use types::*;
/// Returns validator indices which participated in the attestation, sorted by increasing index.
///
/// Spec v0.11.1
pub fn get_attesting_indices<T: EthSpec>(
committee: &[usize],
bitlist: &BitList<T::MaxValidatorsPerCommittee>,
) -> Result<Vec<usize>, BeaconStateError> {
if bitlist.len() != committee.len() {
return Err(BeaconStateError::InvalidBitfield);
}
let mut indices = Vec::with_capacity(bitlist.num_set_bits());
for (i, validator_index) in committee.iter().enumerate() {
if let Ok(true) = bitlist.get(i) {
indices.push(*validator_index)
}
}
indices.sort_unstable();
Ok(indices)
}

View File

@@ -0,0 +1,24 @@
use integer_sqrt::IntegerSquareRoot;
use safe_arith::SafeArith;
use types::*;
/// Returns the base reward for some validator.
///
/// Spec v0.11.1
pub fn get_base_reward<T: EthSpec>(
state: &BeaconState<T>,
index: usize,
// Should be == get_total_active_balance(state, spec)
total_active_balance: u64,
spec: &ChainSpec,
) -> Result<u64, BeaconStateError> {
if total_active_balance == 0 {
Ok(0)
} else {
Ok(state
.get_effective_balance(index, spec)?
.safe_mul(spec.base_reward_factor)?
.safe_div(total_active_balance.integer_sqrt())?
.safe_div(spec.base_rewards_per_epoch)?)
}
}

View File

@@ -0,0 +1,23 @@
use super::get_attesting_indices;
use crate::per_block_processing::errors::{AttestationInvalid as Invalid, BlockOperationError};
use types::*;
type Result<T> = std::result::Result<T, BlockOperationError<Invalid>>;
/// Convert `attestation` to (almost) indexed-verifiable form.
///
/// Spec v0.11.1
pub fn get_indexed_attestation<T: EthSpec>(
committee: &[usize],
attestation: &Attestation<T>,
) -> Result<IndexedAttestation<T>> {
let attesting_indices = get_attesting_indices::<T>(committee, &attestation.aggregation_bits)?;
Ok(IndexedAttestation {
attesting_indices: VariableList::new(
attesting_indices.into_iter().map(|x| x as u64).collect(),
)?,
data: attestation.data.clone(),
signature: attestation.signature.clone(),
})
}

View File

@@ -0,0 +1,42 @@
use std::cmp::max;
use types::{BeaconStateError as Error, *};
/// Initiate the exit of the validator of the given `index`.
///
/// Spec v0.11.1
pub fn initiate_validator_exit<T: EthSpec>(
state: &mut BeaconState<T>,
index: usize,
spec: &ChainSpec,
) -> Result<(), Error> {
if index >= state.validators.len() {
return Err(Error::UnknownValidator(index as u64));
}
// Return if the validator already initiated exit
if state.validators[index].exit_epoch != spec.far_future_epoch {
return Ok(());
}
// Ensure the exit cache is built.
state.exit_cache.build(&state.validators, spec)?;
// Compute exit queue epoch
let delayed_epoch = state.compute_activation_exit_epoch(state.current_epoch(), spec);
let mut exit_queue_epoch = state
.exit_cache
.max_epoch()?
.map_or(delayed_epoch, |epoch| max(epoch, delayed_epoch));
let exit_queue_churn = state.exit_cache.get_churn_at(exit_queue_epoch)?;
if exit_queue_churn >= state.get_churn_limit(spec)? {
exit_queue_epoch += 1;
}
state.exit_cache.record_validator_exit(exit_queue_epoch)?;
state.validators[index].exit_epoch = exit_queue_epoch;
state.validators[index].withdrawable_epoch =
exit_queue_epoch + spec.min_validator_withdrawability_delay;
Ok(())
}

View File

@@ -0,0 +1,34 @@
mod deposit_data_tree;
mod get_attesting_indices;
mod get_base_reward;
mod get_indexed_attestation;
mod initiate_validator_exit;
mod slash_validator;
pub use deposit_data_tree::DepositDataTree;
pub use get_attesting_indices::get_attesting_indices;
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

@@ -0,0 +1,56 @@
use crate::common::{decrease_balance, increase_balance, initiate_validator_exit};
use safe_arith::SafeArith;
use std::cmp;
use types::{BeaconStateError as Error, *};
/// Slash the validator with index ``index``.
///
/// Spec v0.11.1
pub fn slash_validator<T: EthSpec>(
state: &mut BeaconState<T>,
slashed_index: usize,
opt_whistleblower_index: Option<usize>,
spec: &ChainSpec,
) -> Result<(), Error> {
if slashed_index >= state.validators.len() || slashed_index >= state.balances.len() {
return Err(BeaconStateError::UnknownValidator(slashed_index as u64));
}
let epoch = state.current_epoch();
initiate_validator_exit(state, slashed_index, spec)?;
state.validators[slashed_index].slashed = true;
state.validators[slashed_index].withdrawable_epoch = cmp::max(
state.validators[slashed_index].withdrawable_epoch,
epoch + Epoch::from(T::EpochsPerSlashingsVector::to_u64()),
);
let validator_effective_balance = state.get_effective_balance(slashed_index, spec)?;
state.set_slashings(
epoch,
state
.get_slashings(epoch)?
.safe_add(validator_effective_balance)?,
)?;
decrease_balance(
state,
slashed_index,
validator_effective_balance.safe_div(spec.min_slashing_penalty_quotient)?,
);
// Apply proposer and whistleblower rewards
let proposer_index = state.get_beacon_proposer_index(state.slot, spec)?;
let whistleblower_index = opt_whistleblower_index.unwrap_or(proposer_index);
let whistleblower_reward =
validator_effective_balance.safe_div(spec.whistleblower_reward_quotient)?;
let proposer_reward = whistleblower_reward.safe_div(spec.proposer_reward_quotient)?;
increase_balance(state, proposer_index, proposer_reward)?;
increase_balance(
state,
whistleblower_index,
whistleblower_reward.safe_sub(proposer_reward)?,
)?;
Ok(())
}

View File

@@ -0,0 +1,90 @@
use super::per_block_processing::{errors::BlockProcessingError, process_deposit};
use crate::common::DepositDataTree;
use safe_arith::{ArithError, SafeArith};
use tree_hash::TreeHash;
use types::DEPOSIT_TREE_DEPTH;
use types::*;
/// Initialize a `BeaconState` from genesis data.
///
/// Spec v0.11.1
// TODO: this is quite inefficient and we probably want to rethink how we do this
pub fn initialize_beacon_state_from_eth1<T: EthSpec>(
eth1_block_hash: Hash256,
eth1_timestamp: u64,
deposits: Vec<Deposit>,
spec: &ChainSpec,
) -> Result<BeaconState<T>, BlockProcessingError> {
let genesis_time = eth2_genesis_time(eth1_timestamp, spec)?;
let eth1_data = Eth1Data {
// Temporary deposit root
deposit_root: Hash256::zero(),
deposit_count: deposits.len() as u64,
block_hash: eth1_block_hash,
};
let mut state = BeaconState::new(genesis_time, eth1_data, spec);
// Seed RANDAO with Eth1 entropy
state.fill_randao_mixes_with(eth1_block_hash);
let mut deposit_tree = DepositDataTree::create(&[], 0, DEPOSIT_TREE_DEPTH);
for deposit in deposits.iter() {
deposit_tree
.push_leaf(deposit.data.tree_hash_root())
.map_err(BlockProcessingError::MerkleTreeError)?;
state.eth1_data.deposit_root = deposit_tree.root();
process_deposit(&mut state, &deposit, spec, true)?;
}
process_activations(&mut state, spec)?;
// Now that we have our validators, initialize the caches (including the committees)
state.build_all_caches(spec)?;
// Set genesis validators root for domain separation and chain versioning
state.genesis_validators_root = state.update_validators_tree_hash_cache()?;
Ok(state)
}
/// Determine whether a candidate genesis state is suitable for starting the chain.
///
/// Spec v0.11.1
pub fn is_valid_genesis_state<T: EthSpec>(state: &BeaconState<T>, spec: &ChainSpec) -> bool {
state.genesis_time >= spec.min_genesis_time
&& state.get_active_validator_indices(T::genesis_epoch()).len() as u64
>= spec.min_genesis_active_validator_count
}
/// Activate genesis validators, if their balance is acceptable.
///
/// Spec v0.11.1
pub fn process_activations<T: EthSpec>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> Result<(), Error> {
for (index, validator) in state.validators.iter_mut().enumerate() {
let balance = state.balances[index];
validator.effective_balance = std::cmp::min(
balance.safe_sub(balance.safe_rem(spec.effective_balance_increment)?)?,
spec.max_effective_balance,
);
if validator.effective_balance == spec.max_effective_balance {
validator.activation_eligibility_epoch = T::genesis_epoch();
validator.activation_epoch = T::genesis_epoch();
}
}
Ok(())
}
/// Returns the `state.genesis_time` for the corresponding `eth1_timestamp`.
///
/// Does _not_ ensure that the time is greater than `MIN_GENESIS_TIME`.
///
/// Spec v0.11.1
pub fn eth2_genesis_time(eth1_timestamp: u64, spec: &ChainSpec) -> Result<u64, ArithError> {
eth1_timestamp
.safe_sub(eth1_timestamp.safe_rem(spec.min_genesis_delay)?)?
.safe_add(2.safe_mul(spec.min_genesis_delay)?)
}

View File

@@ -0,0 +1,22 @@
#![deny(clippy::integer_arithmetic)]
#[macro_use]
mod macros;
pub mod common;
pub mod genesis;
pub mod per_block_processing;
pub mod per_epoch_processing;
pub mod per_slot_processing;
pub mod test_utils;
pub use genesis::{
eth2_genesis_time, initialize_beacon_state_from_eth1, is_valid_genesis_state,
process_activations,
};
pub use per_block_processing::{
block_signature_verifier, errors::BlockProcessingError, per_block_processing, signature_sets,
BlockSignatureStrategy, BlockSignatureVerifier, VerifySignatures,
};
pub use per_epoch_processing::{errors::EpochProcessingError, per_epoch_processing};
pub use per_slot_processing::{per_slot_processing, Error as SlotProcessingError};

View File

@@ -0,0 +1,15 @@
macro_rules! verify {
($condition: expr, $result: expr) => {
if !$condition {
return Err(crate::per_block_processing::errors::BlockOperationError::invalid($result));
}
};
}
macro_rules! block_verify {
($condition: expr, $result: expr) => {
if !$condition {
return Err($result);
}
};
}

View File

@@ -0,0 +1,508 @@
use crate::common::{increase_balance, initiate_validator_exit, slash_validator};
use errors::{BlockOperationError, BlockProcessingError, HeaderInvalid, IntoWithIndex};
use rayon::prelude::*;
use safe_arith::{ArithError, SafeArith};
use signature_sets::{block_proposal_signature_set, get_pubkey_from_state, randao_signature_set};
use std::convert::TryInto;
use tree_hash::TreeHash;
use types::*;
pub use self::verify_attester_slashing::{
get_slashable_indices, get_slashable_indices_modular, verify_attester_slashing,
};
pub use self::verify_proposer_slashing::verify_proposer_slashing;
pub use block_signature_verifier::BlockSignatureVerifier;
pub use is_valid_indexed_attestation::is_valid_indexed_attestation;
pub use verify_attestation::{
verify_attestation_for_block_inclusion, verify_attestation_for_state,
};
pub use verify_deposit::{
get_existing_validator_index, verify_deposit_merkle_proof, verify_deposit_signature,
};
pub use verify_exit::{verify_exit, verify_exit_time_independent_only};
pub mod block_processing_builder;
pub mod block_signature_verifier;
pub mod errors;
mod is_valid_indexed_attestation;
pub mod signature_sets;
pub mod tests;
mod verify_attestation;
mod verify_attester_slashing;
mod verify_deposit;
mod verify_exit;
mod verify_proposer_slashing;
#[cfg(feature = "arbitrary-fuzz")]
use arbitrary::Arbitrary;
/// The strategy to be used when validating the block's signatures.
#[cfg_attr(feature = "arbitrary-fuzz", derive(Arbitrary))]
#[derive(PartialEq, Clone, Copy)]
pub enum BlockSignatureStrategy {
/// Do not validate any signature. Use with caution.
NoVerification,
/// Validate each signature individually, as its object is being processed.
VerifyIndividual,
/// Verify all signatures in bulk at the beginning of block processing.
VerifyBulk,
}
/// The strategy to be used when validating the block's signatures.
#[cfg_attr(feature = "arbitrary-fuzz", derive(Arbitrary))]
#[derive(PartialEq, Clone, Copy)]
pub enum VerifySignatures {
/// Validate all signatures encountered.
True,
/// Do not validate any signature. Use with caution.
False,
}
impl VerifySignatures {
pub fn is_true(self) -> bool {
self == VerifySignatures::True
}
}
/// Updates the state for a new block, whilst validating that the block is valid, optionally
/// checking the block proposer signature.
///
/// Returns `Ok(())` if the block is valid and the state was successfully updated. Otherwise
/// returns an error describing why the block was invalid or how the function failed to execute.
///
/// If `block_root` is `Some`, this root is used for verification of the proposer's signature. If it
/// is `None` the signing root is computed from scratch. This parameter only exists to avoid
/// re-calculating the root when it is already known. Note `block_root` should be equal to the
/// tree hash root of the block, NOT the signing root of the block. This function takes
/// care of mixing in the domain.
///
/// Spec v0.11.1
pub fn per_block_processing<T: EthSpec>(
mut state: &mut BeaconState<T>,
signed_block: &SignedBeaconBlock<T>,
block_root: Option<Hash256>,
block_signature_strategy: BlockSignatureStrategy,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
let block = &signed_block.message;
let verify_signatures = match block_signature_strategy {
BlockSignatureStrategy::VerifyBulk => {
// Verify all signatures in the block at once.
block_verify!(
BlockSignatureVerifier::verify_entire_block(
state,
|i| get_pubkey_from_state(state, i),
signed_block,
block_root,
spec
)
.is_ok(),
BlockProcessingError::BulkSignatureVerificationFailed
);
VerifySignatures::False
}
BlockSignatureStrategy::VerifyIndividual => VerifySignatures::True,
BlockSignatureStrategy::NoVerification => VerifySignatures::False,
};
process_block_header(state, block, spec)?;
if verify_signatures.is_true() {
verify_block_signature(&state, signed_block, block_root, &spec)?;
}
// Ensure the current and previous epoch caches are built.
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
state.build_committee_cache(RelativeEpoch::Current, spec)?;
process_randao(&mut state, &block, verify_signatures, &spec)?;
process_eth1_data(&mut state, &block.body.eth1_data)?;
process_proposer_slashings(
&mut state,
&block.body.proposer_slashings,
verify_signatures,
spec,
)?;
process_attester_slashings(
&mut state,
&block.body.attester_slashings,
verify_signatures,
spec,
)?;
process_attestations(
&mut state,
&block.body.attestations,
verify_signatures,
spec,
)?;
process_deposits(&mut state, &block.body.deposits, spec)?;
process_exits(
&mut state,
&block.body.voluntary_exits,
verify_signatures,
spec,
)?;
Ok(())
}
/// Processes the block header.
///
/// Spec v0.11.1
pub fn process_block_header<T: EthSpec>(
state: &mut BeaconState<T>,
block: &BeaconBlock<T>,
spec: &ChainSpec,
) -> Result<(), BlockOperationError<HeaderInvalid>> {
// Verify that the slots match
verify!(block.slot == state.slot, HeaderInvalid::StateSlotMismatch);
// Verify that proposer index is the correct index
let proposer_index = block.proposer_index as usize;
let state_proposer_index = state.get_beacon_proposer_index(block.slot, spec)?;
verify!(
proposer_index == state_proposer_index,
HeaderInvalid::ProposerIndexMismatch {
block_proposer_index: proposer_index,
state_proposer_index,
}
);
let expected_previous_block_root = state.latest_block_header.tree_hash_root();
verify!(
block.parent_root == expected_previous_block_root,
HeaderInvalid::ParentBlockRootMismatch {
state: expected_previous_block_root,
block: block.parent_root,
}
);
state.latest_block_header = block.temporary_block_header();
// Verify proposer is not slashed
let proposer = &state.validators[proposer_index];
verify!(
!proposer.slashed,
HeaderInvalid::ProposerSlashed(proposer_index)
);
Ok(())
}
/// Verifies the signature of a block.
///
/// Spec v0.11.1
pub fn verify_block_signature<T: EthSpec>(
state: &BeaconState<T>,
block: &SignedBeaconBlock<T>,
block_root: Option<Hash256>,
spec: &ChainSpec,
) -> Result<(), BlockOperationError<HeaderInvalid>> {
verify!(
block_proposal_signature_set(
state,
|i| get_pubkey_from_state(state, i),
block,
block_root,
spec
)?
.is_valid(),
HeaderInvalid::ProposalSignatureInvalid
);
Ok(())
}
/// Verifies the `randao_reveal` against the block's proposer pubkey and updates
/// `state.latest_randao_mixes`.
///
/// Spec v0.11.1
pub fn process_randao<T: EthSpec>(
state: &mut BeaconState<T>,
block: &BeaconBlock<T>,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
if verify_signatures.is_true() {
// Verify RANDAO reveal signature.
block_verify!(
randao_signature_set(state, |i| get_pubkey_from_state(state, i), block, spec)?
.is_valid(),
BlockProcessingError::RandaoSignatureInvalid
);
}
// Update the current epoch RANDAO mix.
state.update_randao_mix(state.current_epoch(), &block.body.randao_reveal)?;
Ok(())
}
/// Update the `state.eth1_data_votes` based upon the `eth1_data` provided.
///
/// Spec v0.11.1
pub fn process_eth1_data<T: EthSpec>(
state: &mut BeaconState<T>,
eth1_data: &Eth1Data,
) -> Result<(), Error> {
if let Some(new_eth1_data) = get_new_eth1_data(state, eth1_data)? {
state.eth1_data = new_eth1_data;
}
state.eth1_data_votes.push(eth1_data.clone())?;
Ok(())
}
/// Returns `Ok(Some(eth1_data))` if adding the given `eth1_data` to `state.eth1_data_votes` would
/// result in a change to `state.eth1_data`.
///
/// Spec v0.11.1
pub fn get_new_eth1_data<T: EthSpec>(
state: &BeaconState<T>,
eth1_data: &Eth1Data,
) -> Result<Option<Eth1Data>, ArithError> {
let num_votes = state
.eth1_data_votes
.iter()
.filter(|vote| *vote == eth1_data)
.count();
// The +1 is to account for the `eth1_data` supplied to the function.
if num_votes.safe_add(1)?.safe_mul(2)? > T::SlotsPerEth1VotingPeriod::to_usize() {
Ok(Some(eth1_data.clone()))
} else {
Ok(None)
}
}
/// Validates each `ProposerSlashing` and updates the state, short-circuiting on an invalid object.
///
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure.
///
/// Spec v0.11.1
pub fn process_proposer_slashings<T: EthSpec>(
state: &mut BeaconState<T>,
proposer_slashings: &[ProposerSlashing],
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
// Verify and apply proposer slashings in series.
// We have to verify in series because an invalid block may contain multiple slashings
// for the same validator, and we need to correctly detect and reject that.
proposer_slashings
.into_iter()
.enumerate()
.try_for_each(|(i, proposer_slashing)| {
verify_proposer_slashing(proposer_slashing, &state, verify_signatures, spec)
.map_err(|e| e.into_with_index(i))?;
slash_validator(
state,
proposer_slashing.signed_header_1.message.proposer_index as usize,
None,
spec,
)?;
Ok(())
})
}
/// Validates each `AttesterSlashing` and updates the state, short-circuiting on an invalid object.
///
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure.
///
/// Spec v0.11.1
pub fn process_attester_slashings<T: EthSpec>(
state: &mut BeaconState<T>,
attester_slashings: &[AttesterSlashing<T>],
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
for (i, attester_slashing) in attester_slashings.iter().enumerate() {
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))?;
for i in slashable_indices {
slash_validator(state, i as usize, None, spec)?;
}
}
Ok(())
}
/// Validates each `Attestation` and updates the state, short-circuiting on an invalid object.
///
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure.
///
/// Spec v0.11.1
pub fn process_attestations<T: EthSpec>(
state: &mut BeaconState<T>,
attestations: &[Attestation<T>],
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
// Ensure the previous epoch cache exists.
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
let proposer_index = state.get_beacon_proposer_index(state.slot, spec)? as u64;
// 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(),
inclusion_delay: (state.slot - attestation.data.slot).as_u64(),
proposer_index,
};
if attestation.data.target.epoch == state.current_epoch() {
state.current_epoch_attestations.push(pending_attestation)?;
} else {
state
.previous_epoch_attestations
.push(pending_attestation)?;
}
}
Ok(())
}
/// Validates each `Deposit` and updates the state, short-circuiting on an invalid object.
///
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure.
///
/// Spec v0.11.1
pub fn process_deposits<T: EthSpec>(
state: &mut BeaconState<T>,
deposits: &[Deposit],
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
let expected_deposit_len = std::cmp::min(
T::MaxDeposits::to_u64(),
state.get_outstanding_deposit_len()?,
);
block_verify!(
deposits.len() as u64 == expected_deposit_len,
BlockProcessingError::DepositCountInvalid {
expected: expected_deposit_len as usize,
found: deposits.len(),
}
);
// Verify merkle proofs in parallel.
deposits
.par_iter()
.enumerate()
.try_for_each(|(i, deposit)| {
verify_deposit_merkle_proof(
state,
deposit,
state.eth1_deposit_index.safe_add(i as u64)?,
spec,
)
.map_err(|e| e.into_with_index(i))
})?;
// Update the state in series.
for deposit in deposits {
process_deposit(state, deposit, spec, false)?;
}
Ok(())
}
/// Process a single deposit, optionally verifying its merkle proof.
///
/// Spec v0.11.1
pub fn process_deposit<T: EthSpec>(
state: &mut BeaconState<T>,
deposit: &Deposit,
spec: &ChainSpec,
verify_merkle_proof: bool,
) -> Result<(), BlockProcessingError> {
let deposit_index = state.eth1_deposit_index as usize;
if verify_merkle_proof {
verify_deposit_merkle_proof(state, deposit, state.eth1_deposit_index, spec)
.map_err(|e| e.into_with_index(deposit_index))?;
}
state.eth1_deposit_index.increment()?;
// Ensure the state's pubkey cache is fully up-to-date, it will be used to check to see if the
// depositing validator already exists in the registry.
state.update_pubkey_cache()?;
let pubkey: PublicKey = match (&deposit.data.pubkey).try_into() {
Err(_) => return Ok(()), //bad public key => return early
Ok(k) => k,
};
// Get an `Option<u64>` where `u64` is the validator index if this deposit public key
// already exists in the beacon_state.
let validator_index = get_existing_validator_index(state, &deposit.data.pubkey)
.map_err(|e| e.into_with_index(deposit_index))?;
let amount = deposit.data.amount;
if let Some(index) = validator_index {
// Update the existing validator balance.
increase_balance(state, index as usize, amount)?;
} else {
// The signature should be checked for new validators. Return early for a bad
// signature.
if verify_deposit_signature(&deposit.data, spec).is_err() {
return Ok(());
}
// Create a new validator.
let validator = Validator {
pubkey: pubkey.into(),
withdrawal_credentials: deposit.data.withdrawal_credentials,
activation_eligibility_epoch: spec.far_future_epoch,
activation_epoch: spec.far_future_epoch,
exit_epoch: spec.far_future_epoch,
withdrawable_epoch: spec.far_future_epoch,
effective_balance: std::cmp::min(
amount.safe_sub(amount.safe_rem(spec.effective_balance_increment)?)?,
spec.max_effective_balance,
),
slashed: false,
};
state.validators.push(validator)?;
state.balances.push(deposit.data.amount)?;
}
Ok(())
}
/// Validates each `Exit` and updates the state, short-circuiting on an invalid object.
///
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure.
///
/// Spec v0.11.1
pub fn process_exits<T: EthSpec>(
state: &mut BeaconState<T>,
voluntary_exits: &[SignedVoluntaryExit],
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
// 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))?;
initiate_validator_exit(state, exit.message.validator_index as usize, spec)?;
}
Ok(())
}

View File

@@ -0,0 +1,380 @@
use tree_hash::TreeHash;
use types::test_utils::{
AttestationTestTask, AttesterSlashingTestTask, DepositTestTask, ProposerSlashingTestTask,
TestingAttestationDataBuilder, TestingBeaconBlockBuilder, TestingBeaconStateBuilder,
};
use types::*;
pub struct BlockProcessingBuilder<'a, T: EthSpec> {
pub state: BeaconState<T>,
pub keypairs: Vec<Keypair>,
pub block_builder: TestingBeaconBlockBuilder<T>,
pub spec: &'a ChainSpec,
}
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,
keypairs,
block_builder,
spec,
}
}
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(
mut self,
num_deposits: u64,
test_task: DepositTestTask,
randao_sk: Option<SecretKey>,
previous_block_root: Option<Hash256>,
spec: &ChainSpec,
) -> (SignedBeaconBlock<T>, BeaconState<T>) {
let (mut state, keypairs) = (self.state, self.keypairs);
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,
),
}
self.block_builder.insert_deposits(
spec.max_effective_balance,
test_task,
1,
num_deposits,
&mut state,
spec,
);
let block = self.block_builder.build(
&keypair.sk,
&state.fork,
state.genesis_validators_root,
spec,
);
(block, state)
}
/// 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
}
/// 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(
mut self,
test_task: AttestationTestTask,
num_attestations: u64,
randao_sk: Option<SecretKey>,
previous_block_root: Option<Hash256>,
spec: &ChainSpec,
) -> (SignedBeaconBlock<T>, BeaconState<T>) {
let (state, keypairs) = (self.state, self.keypairs);
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,
),
}
let all_secret_keys: Vec<&SecretKey> = keypairs.iter().map(|keypair| &keypair.sk).collect();
self.block_builder
.insert_attestations(
test_task,
&state,
&all_secret_keys,
num_attestations as usize,
spec,
)
.unwrap();
let block = self.block_builder.build(
&keypair.sk,
&state.fork,
state.genesis_validators_root,
spec,
);
(block, state)
}
pub fn build_with_attester_slashing(
mut self,
test_task: AttesterSlashingTestTask,
num_attester_slashings: u64,
randao_sk: Option<SecretKey>,
previous_block_root: Option<Hash256>,
spec: &ChainSpec,
) -> (SignedBeaconBlock<T>, BeaconState<T>) {
let (state, keypairs) = (self.state, self.keypairs);
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,
),
}
let mut validator_indices = vec![];
let mut secret_keys = vec![];
for i in 0..num_attester_slashings {
validator_indices.push(i);
secret_keys.push(&keypairs[i as usize].sk);
}
for _ in 0..num_attester_slashings {
self.block_builder.insert_attester_slashing(
test_task,
&validator_indices,
&secret_keys,
&state.fork,
state.genesis_validators_root,
spec,
);
}
let block = self.block_builder.build(
&keypair.sk,
&state.fork,
state.genesis_validators_root,
spec,
);
(block, state)
}
pub fn build_with_proposer_slashing(
mut self,
test_task: ProposerSlashingTestTask,
num_proposer_slashings: u64,
randao_sk: Option<SecretKey>,
previous_block_root: Option<Hash256>,
spec: &ChainSpec,
) -> (SignedBeaconBlock<T>, BeaconState<T>) {
let (state, keypairs) = (self.state, self.keypairs);
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,
),
}
for i in 0..num_proposer_slashings {
let validator_indices = i;
let secret_keys = &keypairs[i as usize].sk;
self.block_builder.insert_proposer_slashing(
test_task,
validator_indices,
&secret_keys,
&state.fork,
state.genesis_validators_root,
spec,
);
}
let block = self.block_builder.build(
&keypair.sk,
&state.fork,
state.genesis_validators_root,
spec,
);
(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>,
) -> (SignedBeaconBlock<T>, BeaconState<T>) {
let (state, keypairs) = (self.state, self.keypairs);
let spec = self.spec;
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,
),
}
let block = self.block_builder.build(
&keypair.sk,
&state.fork,
state.genesis_validators_root,
spec,
);
(block, state)
}
}

View File

@@ -0,0 +1,296 @@
#![allow(clippy::integer_arithmetic)]
use super::signature_sets::{Error as SignatureSetError, Result as SignatureSetResult, *};
use crate::common::get_indexed_attestation;
use crate::per_block_processing::errors::{AttestationInvalid, BlockOperationError};
use bls::{verify_signature_sets, PublicKey, SignatureSet};
use rayon::prelude::*;
use std::borrow::Cow;
use types::{
BeaconState, BeaconStateError, ChainSpec, EthSpec, Hash256, IndexedAttestation,
SignedBeaconBlock,
};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, PartialEq)]
pub enum Error {
/// All public keys were found but signature verification failed. The block is invalid.
SignatureInvalid,
/// An attestation in the block was invalid. The block is invalid.
AttestationValidationError(BlockOperationError<AttestationInvalid>),
/// There was an error attempting to read from a `BeaconState`. Block
/// validity was not determined.
BeaconStateError(BeaconStateError),
/// The `BeaconBlock` has a `proposer_index` that does not match the index we computed locally.
///
/// The block is invalid.
IncorrectBlockProposer { block: u64, local_shuffling: u64 },
/// Failed to load a signature set. The block may be invalid or we failed to process it.
SignatureSetError(SignatureSetError),
}
impl From<BeaconStateError> for Error {
fn from(e: BeaconStateError) -> Error {
Error::BeaconStateError(e)
}
}
impl From<SignatureSetError> for Error {
fn from(e: SignatureSetError) -> Error {
match e {
// Make a special distinction for `IncorrectBlockProposer` since it indicates an
// invalid block, not an internal error.
SignatureSetError::IncorrectBlockProposer {
block,
local_shuffling,
} => Error::IncorrectBlockProposer {
block,
local_shuffling,
},
e => Error::SignatureSetError(e),
}
}
}
impl From<BlockOperationError<AttestationInvalid>> for Error {
fn from(e: BlockOperationError<AttestationInvalid>) -> Error {
Error::AttestationValidationError(e)
}
}
/// Reads the BLS signatures and keys from a `SignedBeaconBlock`, storing them as a `Vec<SignatureSet>`.
///
/// This allows for optimizations related to batch BLS operations (see the
/// `Self::verify_entire_block(..)` function).
pub struct BlockSignatureVerifier<'a, T, F>
where
T: EthSpec,
F: Fn(usize) -> Option<Cow<'a, PublicKey>> + Clone,
{
get_pubkey: F,
state: &'a BeaconState<T>,
spec: &'a ChainSpec,
sets: Vec<SignatureSet>,
}
impl<'a, T, F> BlockSignatureVerifier<'a, T, F>
where
T: EthSpec,
F: Fn(usize) -> Option<Cow<'a, PublicKey>> + Clone,
{
/// Create a new verifier without any included signatures. See the `include...` functions to
/// add signatures, and the `verify`
pub fn new(state: &'a BeaconState<T>, get_pubkey: F, spec: &'a ChainSpec) -> Self {
Self {
get_pubkey: get_pubkey,
state,
spec,
sets: vec![],
}
}
/// Verify all* the signatures in the given `SignedBeaconBlock`, returning `Ok(())` if the signatures
/// are valid.
///
/// * : _Does not verify any signatures in `block.body.deposits`. A block is still valid if it
/// contains invalid signatures on deposits._
///
/// See `Self::verify` for more detail.
pub fn verify_entire_block(
state: &'a BeaconState<T>,
get_pubkey: F,
block: &'a SignedBeaconBlock<T>,
block_root: Option<Hash256>,
spec: &'a ChainSpec,
) -> Result<()> {
let mut verifier = Self::new(state, get_pubkey, spec);
verifier.include_all_signatures(block, block_root)?;
verifier.verify()
}
/// Verify all* the signatures that have been included in `self`, returning `Ok(())` if the
/// signatures are all valid.
///
/// ## Notes
///
/// Signature validation will take place in accordance to the [Faster verification of multiple
/// BLS signatures](https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407)
/// optimization proposed by Vitalik Buterin.
///
/// It is not possible to know exactly _which_ signature is invalid here, just that
/// _at least one_ was invalid.
///
/// Uses `rayon` to do a map-reduce of Vitalik's method across multiple cores.
pub fn verify(self) -> Result<()> {
let num_sets = self.sets.len();
let num_chunks = std::cmp::max(1, num_sets / rayon::current_num_threads());
let result: bool = self
.sets
.into_par_iter()
.chunks(num_chunks)
.map(|chunk| verify_signature_sets(chunk))
.reduce(|| true, |current, this| current && this);
if result {
Ok(())
} else {
Err(Error::SignatureInvalid)
}
}
/// Includes all signatures on the block (except the deposit signatures) for verification.
pub fn include_all_signatures(
&mut self,
block: &'a SignedBeaconBlock<T>,
block_root: Option<Hash256>,
) -> Result<()> {
self.include_block_proposal(block, block_root)?;
self.include_randao_reveal(block)?;
self.include_proposer_slashings(block)?;
self.include_attester_slashings(block)?;
self.include_attestations(block)?;
// Deposits are not included because they can legally have invalid signatures.
self.include_exits(block)?;
Ok(())
}
/// Includes all signatures on the block (except the deposit signatures and the proposal
/// signature) for verification.
pub fn include_all_signatures_except_proposal(
&mut self,
block: &'a SignedBeaconBlock<T>,
) -> Result<()> {
self.include_randao_reveal(block)?;
self.include_proposer_slashings(block)?;
self.include_attester_slashings(block)?;
self.include_attestations(block)?;
// Deposits are not included because they can legally have invalid signatures.
self.include_exits(block)?;
Ok(())
}
/// Includes the block signature for `self.block` for verification.
pub fn include_block_proposal(
&mut self,
block: &'a SignedBeaconBlock<T>,
block_root: Option<Hash256>,
) -> Result<()> {
let set = block_proposal_signature_set(
self.state,
self.get_pubkey.clone(),
block,
block_root,
self.spec,
)?;
self.sets.push(set);
Ok(())
}
/// Includes the randao signature for `self.block` for verification.
pub fn include_randao_reveal(&mut self, block: &'a SignedBeaconBlock<T>) -> Result<()> {
let set = randao_signature_set(
self.state,
self.get_pubkey.clone(),
&block.message,
self.spec,
)?;
self.sets.push(set);
Ok(())
}
/// Includes all signatures in `self.block.body.proposer_slashings` for verification.
pub fn include_proposer_slashings(&mut self, block: &'a SignedBeaconBlock<T>) -> Result<()> {
let mut sets: Vec<SignatureSet> = block
.message
.body
.proposer_slashings
.iter()
.map(|proposer_slashing| {
let (set_1, set_2) = proposer_slashing_signature_set(
self.state,
self.get_pubkey.clone(),
proposer_slashing,
self.spec,
)?;
Ok(vec![set_1, set_2])
})
.collect::<SignatureSetResult<Vec<Vec<SignatureSet>>>>()?
.into_iter()
.flatten()
.collect();
self.sets.append(&mut sets);
Ok(())
}
/// Includes all signatures in `self.block.body.attester_slashings` for verification.
pub fn include_attester_slashings(&mut self, block: &'a SignedBeaconBlock<T>) -> Result<()> {
block
.message
.body
.attester_slashings
.iter()
.try_for_each(|attester_slashing| {
let (set_1, set_2) = attester_slashing_signature_sets(
&self.state,
self.get_pubkey.clone(),
attester_slashing,
&self.spec,
)?;
self.sets.push(set_1);
self.sets.push(set_2);
Ok(())
})
}
/// Includes all signatures in `self.block.body.attestations` for verification.
pub fn include_attestations(
&mut self,
block: &'a SignedBeaconBlock<T>,
) -> Result<Vec<IndexedAttestation<T>>> {
block
.message
.body
.attestations
.iter()
.map(|attestation| {
let committee = self
.state
.get_beacon_committee(attestation.data.slot, attestation.data.index)?;
let indexed_attestation =
get_indexed_attestation(committee.committee, attestation)?;
self.sets.push(indexed_attestation_signature_set(
&self.state,
self.get_pubkey.clone(),
&attestation.signature,
&indexed_attestation,
&self.spec,
)?);
Ok(indexed_attestation)
})
.collect::<Result<_>>()
.map_err(Into::into)
}
/// Includes all signatures in `self.block.body.voluntary_exits` for verification.
pub fn include_exits(&mut self, block: &'a SignedBeaconBlock<T>) -> Result<()> {
let mut sets = block
.message
.body
.voluntary_exits
.iter()
.map(|exit| exit_signature_set(&self.state, self.get_pubkey.clone(), exit, &self.spec))
.collect::<SignatureSetResult<_>>()?;
self.sets.append(&mut sets);
Ok(())
}
}

View File

@@ -0,0 +1,344 @@
use super::signature_sets::Error as SignatureSetError;
use merkle_proof::MerkleTreeError;
use safe_arith::ArithError;
use types::*;
/// The error returned from the `per_block_processing` function. Indicates that a block is either
/// invalid, or we were unable to determine its validity (we encountered an unexpected error).
///
/// Any of the `...Error` variants indicate that at some point during block (and block operation)
/// verification, there was an error. There is no indication as to _where_ that error happened
/// (e.g., when processing attestations instead of when processing deposits).
#[derive(Debug, PartialEq, Clone)]
pub enum BlockProcessingError {
RandaoSignatureInvalid,
BulkSignatureVerificationFailed,
StateRootMismatch,
DepositCountInvalid {
expected: usize,
found: usize,
},
HeaderInvalid {
reason: HeaderInvalid,
},
ProposerSlashingInvalid {
index: usize,
reason: ProposerSlashingInvalid,
},
AttesterSlashingInvalid {
index: usize,
reason: AttesterSlashingInvalid,
},
IndexedAttestationInvalid {
index: usize,
reason: IndexedAttestationInvalid,
},
AttestationInvalid {
index: usize,
reason: AttestationInvalid,
},
DepositInvalid {
index: usize,
reason: DepositInvalid,
},
ExitInvalid {
index: usize,
reason: ExitInvalid,
},
BeaconStateError(BeaconStateError),
SignatureSetError(SignatureSetError),
SszTypesError(ssz_types::Error),
MerkleTreeError(MerkleTreeError),
ArithError(ArithError),
}
impl From<BeaconStateError> for BlockProcessingError {
fn from(e: BeaconStateError) -> Self {
BlockProcessingError::BeaconStateError(e)
}
}
impl From<SignatureSetError> for BlockProcessingError {
fn from(e: SignatureSetError) -> Self {
BlockProcessingError::SignatureSetError(e)
}
}
impl From<ssz_types::Error> for BlockProcessingError {
fn from(error: ssz_types::Error) -> Self {
BlockProcessingError::SszTypesError(error)
}
}
impl From<ArithError> for BlockProcessingError {
fn from(e: ArithError) -> Self {
BlockProcessingError::ArithError(e)
}
}
impl From<BlockOperationError<HeaderInvalid>> for BlockProcessingError {
fn from(e: BlockOperationError<HeaderInvalid>) -> BlockProcessingError {
match e {
BlockOperationError::Invalid(reason) => BlockProcessingError::HeaderInvalid { reason },
BlockOperationError::BeaconStateError(e) => BlockProcessingError::BeaconStateError(e),
BlockOperationError::SignatureSetError(e) => BlockProcessingError::SignatureSetError(e),
BlockOperationError::SszTypesError(e) => BlockProcessingError::SszTypesError(e),
BlockOperationError::ArithError(e) => BlockProcessingError::ArithError(e),
}
}
}
/// A conversion that consumes `self` and adds an `index` variable to resulting struct.
///
/// Used here to allow converting an error into an upstream error that points to the object that
/// caused the error. For example, pointing to the index of an attestation that caused the
/// `AttestationInvalid` error.
pub trait IntoWithIndex<T>: Sized {
fn into_with_index(self, index: usize) -> T;
}
macro_rules! impl_into_block_processing_error_with_index {
($($type: ident),*) => {
$(
impl IntoWithIndex<BlockProcessingError> for BlockOperationError<$type> {
fn into_with_index(self, index: usize) -> BlockProcessingError {
match self {
BlockOperationError::Invalid(reason) => BlockProcessingError::$type {
index,
reason
},
BlockOperationError::BeaconStateError(e) => BlockProcessingError::BeaconStateError(e),
BlockOperationError::SignatureSetError(e) => BlockProcessingError::SignatureSetError(e),
BlockOperationError::SszTypesError(e) => BlockProcessingError::SszTypesError(e),
BlockOperationError::ArithError(e) => BlockProcessingError::ArithError(e),
}
}
}
)*
};
}
impl_into_block_processing_error_with_index!(
ProposerSlashingInvalid,
AttesterSlashingInvalid,
IndexedAttestationInvalid,
AttestationInvalid,
DepositInvalid,
ExitInvalid
);
pub type HeaderValidationError = BlockOperationError<HeaderInvalid>;
pub type AttesterSlashingValidationError = BlockOperationError<AttesterSlashingInvalid>;
pub type ProposerSlashingValidationError = BlockOperationError<ProposerSlashingInvalid>;
pub type AttestationValidationError = BlockOperationError<AttestationInvalid>;
pub type DepositValidationError = BlockOperationError<DepositInvalid>;
pub type ExitValidationError = BlockOperationError<ExitInvalid>;
#[derive(Debug, PartialEq, Clone)]
pub enum BlockOperationError<T> {
Invalid(T),
BeaconStateError(BeaconStateError),
SignatureSetError(SignatureSetError),
SszTypesError(ssz_types::Error),
ArithError(ArithError),
}
impl<T> BlockOperationError<T> {
pub fn invalid(reason: T) -> BlockOperationError<T> {
BlockOperationError::Invalid(reason)
}
}
impl<T> From<BeaconStateError> for BlockOperationError<T> {
fn from(e: BeaconStateError) -> Self {
BlockOperationError::BeaconStateError(e)
}
}
impl<T> From<SignatureSetError> for BlockOperationError<T> {
fn from(e: SignatureSetError) -> Self {
BlockOperationError::SignatureSetError(e)
}
}
impl<T> From<ssz_types::Error> for BlockOperationError<T> {
fn from(error: ssz_types::Error) -> Self {
BlockOperationError::SszTypesError(error)
}
}
impl<T> From<ArithError> for BlockOperationError<T> {
fn from(e: ArithError) -> Self {
BlockOperationError::ArithError(e)
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum HeaderInvalid {
ProposalSignatureInvalid,
StateSlotMismatch,
ProposerIndexMismatch {
block_proposer_index: usize,
state_proposer_index: usize,
},
ParentBlockRootMismatch {
state: Hash256,
block: Hash256,
},
ProposerSlashed(usize),
}
#[derive(Debug, PartialEq, Clone)]
pub enum ProposerSlashingInvalid {
/// The proposer index is not a known validator.
ProposerUnknown(u64),
/// The two proposal have different slots.
///
/// (proposal_1_slot, proposal_2_slot)
ProposalSlotMismatch(Slot, Slot),
/// The two proposals have different proposer indices.
///
/// (proposer_index_1, proposer_index_2)
ProposerIndexMismatch(u64, u64),
/// The proposals are identical and therefore not slashable.
ProposalsIdentical,
/// The specified proposer cannot be slashed because they are already slashed, or not active.
ProposerNotSlashable(u64),
/// The first proposal signature was invalid.
BadProposal1Signature,
/// The second proposal signature was invalid.
BadProposal2Signature,
}
#[derive(Debug, PartialEq, Clone)]
pub enum AttesterSlashingInvalid {
/// The attestations were not in conflict.
NotSlashable,
/// The first `IndexedAttestation` was invalid.
IndexedAttestation1Invalid(BlockOperationError<IndexedAttestationInvalid>),
/// The second `IndexedAttestation` was invalid.
IndexedAttestation2Invalid(BlockOperationError<IndexedAttestationInvalid>),
/// The validator index is unknown. One cannot slash one who does not exist.
UnknownValidator(u64),
/// The specified validator has already been withdrawn.
ValidatorAlreadyWithdrawn(u64),
/// There were no indices able to be slashed.
NoSlashableIndices,
}
/// Describes why an object is invalid.
#[derive(Debug, PartialEq, Clone)]
pub enum AttestationInvalid {
/// Commmittee index exceeds number of committees in that slot.
BadCommitteeIndex,
/// Attestation included before the inclusion delay.
IncludedTooEarly {
state: Slot,
delay: u64,
attestation: Slot,
},
/// Attestation slot is too far in the past to be included in a block.
IncludedTooLate { state: Slot, attestation: Slot },
/// Attestation target epoch does not match attestation slot.
TargetEpochSlotMismatch {
target_epoch: Epoch,
slot_epoch: Epoch,
},
/// Attestation target epoch does not match the current or previous epoch.
BadTargetEpoch,
/// Attestation justified checkpoint doesn't match the state's current or previous justified
/// checkpoint.
///
/// `is_current` is `true` if the attestation was compared to the
/// `state.current_justified_checkpoint`, `false` if compared to `state.previous_justified_checkpoint`.
WrongJustifiedCheckpoint {
state: Checkpoint,
attestation: Checkpoint,
is_current: bool,
},
/// There are no set bits on the attestation -- an attestation must be signed by at least one
/// validator.
AggregationBitfieldIsEmpty,
/// The aggregation bitfield length is not the smallest possible size to represent the committee.
BadAggregationBitfieldLength {
committee_len: usize,
bitfield_len: usize,
},
/// The attestation was not disjoint compared to already seen attestations.
NotDisjoint,
/// The validator index was unknown.
UnknownValidator(u64),
/// The attestation signature verification failed.
BadSignature,
/// The indexed attestation created from this attestation was found to be invalid.
BadIndexedAttestation(IndexedAttestationInvalid),
}
impl From<BlockOperationError<IndexedAttestationInvalid>>
for BlockOperationError<AttestationInvalid>
{
fn from(e: BlockOperationError<IndexedAttestationInvalid>) -> Self {
match e {
BlockOperationError::Invalid(e) => {
BlockOperationError::invalid(AttestationInvalid::BadIndexedAttestation(e))
}
BlockOperationError::BeaconStateError(e) => BlockOperationError::BeaconStateError(e),
BlockOperationError::SignatureSetError(e) => BlockOperationError::SignatureSetError(e),
BlockOperationError::SszTypesError(e) => BlockOperationError::SszTypesError(e),
BlockOperationError::ArithError(e) => BlockOperationError::ArithError(e),
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum IndexedAttestationInvalid {
/// The number of indices exceeds the global maximum.
///
/// (max_indices, indices_given)
MaxIndicesExceed(usize, usize),
/// The validator indices were not in increasing order.
///
/// The error occurred between the given `index` and `index + 1`
BadValidatorIndicesOrdering(usize),
/// The validator index is unknown. One cannot slash one who does not exist.
UnknownValidator(u64),
/// The indexed attestation aggregate signature was not valid.
BadSignature,
/// There was an error whilst attempting to get a set of signatures. The signatures may have
/// been invalid or an internal error occurred.
SignatureSetError(SignatureSetError),
}
#[derive(Debug, PartialEq, Clone)]
pub enum DepositInvalid {
/// The signature (proof-of-possession) does not match the given pubkey.
BadSignature,
/// The signature or pubkey does not represent a valid BLS point.
BadBlsBytes,
/// The specified `branch` and `index` did not form a valid proof that the deposit is included
/// in the eth1 deposit root.
BadMerkleProof,
}
#[derive(Debug, PartialEq, Clone)]
pub enum ExitInvalid {
/// The specified validator is not active.
NotActive(u64),
/// The specified validator is not in the state's validator registry.
ValidatorUnknown(u64),
/// The specified validator has a non-maximum exit epoch.
AlreadyExited(u64),
/// The specified validator has already initiated exit.
AlreadyInitiatedExit(u64),
/// The exit is for a future epoch.
FutureEpoch { state: Epoch, exit: Epoch },
/// The validator has not been active for long enough.
TooYoungToExit {
current_epoch: Epoch,
earliest_exit_epoch: Epoch,
},
/// The exit signature was not signed by the validator.
BadSignature,
/// There was an error whilst attempting to get a set of signatures. The signatures may have
/// been invalid or an internal error occurred.
SignatureSetError(SignatureSetError),
}

View File

@@ -0,0 +1,57 @@
use super::errors::{BlockOperationError, IndexedAttestationInvalid as Invalid};
use super::signature_sets::{get_pubkey_from_state, indexed_attestation_signature_set};
use crate::VerifySignatures;
use types::*;
type Result<T> = std::result::Result<T, BlockOperationError<Invalid>>;
fn error(reason: Invalid) -> BlockOperationError<Invalid> {
BlockOperationError::invalid(reason)
}
/// Verify an `IndexedAttestation`.
///
/// Spec v0.11.1
pub fn is_valid_indexed_attestation<T: EthSpec>(
state: &BeaconState<T>,
indexed_attestation: &IndexedAttestation<T>,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<()> {
let indices = &indexed_attestation.attesting_indices;
// Verify max number of indices
verify!(
indices.len() <= T::MaxValidatorsPerCommittee::to_usize(),
Invalid::MaxIndicesExceed(T::MaxValidatorsPerCommittee::to_usize(), indices.len())
);
// Check that indices are sorted and unique
let check_sorted = |list: &[u64]| -> Result<()> {
list.windows(2).enumerate().try_for_each(|(i, pair)| {
if pair[0] < pair[1] {
Ok(())
} else {
Err(error(Invalid::BadValidatorIndicesOrdering(i)))
}
})?;
Ok(())
};
check_sorted(indices)?;
if verify_signatures.is_true() {
verify!(
indexed_attestation_signature_set(
state,
|i| get_pubkey_from_state(state, i),
&indexed_attestation.signature,
&indexed_attestation,
spec
)?
.is_valid(),
Invalid::BadSignature
);
}
Ok(())
}

View File

@@ -0,0 +1,427 @@
//! A `SignatureSet` is an abstraction over the components of a signature. A `SignatureSet` may be
//! validated individually, or alongside in others in a potentially cheaper bulk operation.
//!
//! This module exposes one function to extract each type of `SignatureSet` from a `BeaconBlock`.
use bls::SignatureSet;
use ssz::DecodeError;
use std::borrow::Cow;
use std::convert::TryInto;
use tree_hash::TreeHash;
use types::{
AggregateSignature, AttesterSlashing, BeaconBlock, BeaconState, BeaconStateError, ChainSpec,
DepositData, Domain, EthSpec, Fork, Hash256, IndexedAttestation, ProposerSlashing, PublicKey,
Signature, SignedAggregateAndProof, SignedBeaconBlock, SignedBeaconBlockHeader, SignedRoot,
SignedVoluntaryExit, SigningRoot,
};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, PartialEq, Clone)]
pub enum Error {
/// Signature verification failed. The block is invalid.
SignatureInvalid(DecodeError),
/// There was an error attempting to read from a `BeaconState`. Block
/// validity was not determined.
BeaconStateError(BeaconStateError),
/// Attempted to find the public key of a validator that does not exist. You cannot distinguish
/// between an error and an invalid block in this case.
ValidatorUnknown(u64),
/// The `BeaconBlock` has a `proposer_index` that does not match the index we computed locally.
///
/// The block is invalid.
IncorrectBlockProposer { block: u64, local_shuffling: u64 },
/// The public keys supplied do not match the number of objects requiring keys. Block validity
/// was not determined.
MismatchedPublicKeyLen { pubkey_len: usize, other_len: usize },
/// The public key bytes stored in the `BeaconState` were not valid. This is a serious internal
/// error.
BadBlsBytes { validator_index: u64 },
}
impl From<BeaconStateError> for Error {
fn from(e: BeaconStateError) -> Error {
Error::BeaconStateError(e)
}
}
/// Helper function to get a public key from a `state`.
pub fn get_pubkey_from_state<'a, T>(
state: &'a BeaconState<T>,
validator_index: usize,
) -> Option<Cow<'a, PublicKey>>
where
T: EthSpec,
{
state
.validators
.get(validator_index)
.and_then(|v| {
let pk: Option<PublicKey> = (&v.pubkey).try_into().ok();
pk
})
.map(Cow::Owned)
}
/// A signature set that is valid if a block was signed by the expected block producer.
pub fn block_proposal_signature_set<'a, T, F>(
state: &'a BeaconState<T>,
get_pubkey: F,
signed_block: &'a SignedBeaconBlock<T>,
block_root: Option<Hash256>,
spec: &'a ChainSpec,
) -> Result<SignatureSet>
where
T: EthSpec,
F: Fn(usize) -> Option<Cow<'a, PublicKey>>,
{
let block = &signed_block.message;
let proposer_index = state.get_beacon_proposer_index(block.slot, spec)?;
if proposer_index as u64 != block.proposer_index {
return Err(Error::IncorrectBlockProposer {
block: block.proposer_index,
local_shuffling: proposer_index as u64,
});
}
let domain = spec.get_domain(
block.slot.epoch(T::slots_per_epoch()),
Domain::BeaconProposer,
&state.fork,
state.genesis_validators_root,
);
let message = if let Some(root) = block_root {
SigningRoot {
object_root: root,
domain,
}
.tree_hash_root()
} else {
block.signing_root(domain)
};
Ok(SignatureSet::single(
&signed_block.signature,
get_pubkey(proposer_index).ok_or_else(|| Error::ValidatorUnknown(proposer_index as u64))?,
message.as_bytes().to_vec(),
))
}
/// A signature set that is valid if the block proposers randao reveal signature is correct.
pub fn randao_signature_set<'a, T, F>(
state: &'a BeaconState<T>,
get_pubkey: F,
block: &'a BeaconBlock<T>,
spec: &'a ChainSpec,
) -> Result<SignatureSet>
where
T: EthSpec,
F: Fn(usize) -> Option<Cow<'a, PublicKey>>,
{
let proposer_index = state.get_beacon_proposer_index(block.slot, spec)?;
let domain = spec.get_domain(
block.slot.epoch(T::slots_per_epoch()),
Domain::Randao,
&state.fork,
state.genesis_validators_root,
);
let message = block.slot.epoch(T::slots_per_epoch()).signing_root(domain);
Ok(SignatureSet::single(
&block.body.randao_reveal,
get_pubkey(proposer_index).ok_or_else(|| Error::ValidatorUnknown(proposer_index as u64))?,
message.as_bytes().to_vec(),
))
}
/// Returns two signature sets, one for each `BlockHeader` included in the `ProposerSlashing`.
pub fn proposer_slashing_signature_set<'a, T, F>(
state: &'a BeaconState<T>,
get_pubkey: F,
proposer_slashing: &'a ProposerSlashing,
spec: &'a ChainSpec,
) -> Result<(SignatureSet, SignatureSet)>
where
T: EthSpec,
F: Fn(usize) -> Option<Cow<'a, PublicKey>>,
{
let proposer_index = proposer_slashing.signed_header_1.message.proposer_index as usize;
Ok((
block_header_signature_set(
state,
&proposer_slashing.signed_header_1,
get_pubkey(proposer_index)
.ok_or_else(|| Error::ValidatorUnknown(proposer_index as u64))?,
spec,
)?,
block_header_signature_set(
state,
&proposer_slashing.signed_header_2,
get_pubkey(proposer_index)
.ok_or_else(|| Error::ValidatorUnknown(proposer_index as u64))?,
spec,
)?,
))
}
/// Returns a signature set that is valid if the given `pubkey` signed the `header`.
fn block_header_signature_set<'a, T: EthSpec>(
state: &'a BeaconState<T>,
signed_header: &'a SignedBeaconBlockHeader,
pubkey: Cow<'a, PublicKey>,
spec: &'a ChainSpec,
) -> Result<SignatureSet> {
let domain = spec.get_domain(
signed_header.message.slot.epoch(T::slots_per_epoch()),
Domain::BeaconProposer,
&state.fork,
state.genesis_validators_root,
);
let message = signed_header
.message
.signing_root(domain)
.as_bytes()
.to_vec();
Ok(SignatureSet::single(
&signed_header.signature,
pubkey,
message,
))
}
/// Returns the signature set for the given `indexed_attestation`.
pub fn indexed_attestation_signature_set<'a, 'b, T, F>(
state: &'a BeaconState<T>,
get_pubkey: F,
signature: &'a AggregateSignature,
indexed_attestation: &'b IndexedAttestation<T>,
spec: &'a ChainSpec,
) -> Result<SignatureSet>
where
T: EthSpec,
F: Fn(usize) -> Option<Cow<'a, PublicKey>>,
{
let pubkeys = indexed_attestation
.attesting_indices
.into_iter()
.map(|&validator_idx| {
Ok(get_pubkey(validator_idx as usize)
.ok_or_else(|| Error::ValidatorUnknown(validator_idx))?)
})
.collect::<Result<_>>()?;
let domain = spec.get_domain(
indexed_attestation.data.target.epoch,
Domain::BeaconAttester,
&state.fork,
state.genesis_validators_root,
);
let message = indexed_attestation.data.signing_root(domain);
let message = message.as_bytes().to_vec();
Ok(SignatureSet::new(signature, pubkeys, message))
}
/// Returns the signature set for the given `indexed_attestation` but pubkeys are supplied directly
/// instead of from the state.
pub fn indexed_attestation_signature_set_from_pubkeys<'a, 'b, T, F>(
get_pubkey: F,
signature: &'a AggregateSignature,
indexed_attestation: &'b IndexedAttestation<T>,
fork: &Fork,
genesis_validators_root: Hash256,
spec: &'a ChainSpec,
) -> Result<SignatureSet>
where
T: EthSpec,
F: Fn(usize) -> Option<Cow<'a, PublicKey>>,
{
let pubkeys = indexed_attestation
.attesting_indices
.into_iter()
.map(|&validator_idx| {
Ok(get_pubkey(validator_idx as usize)
.ok_or_else(|| Error::ValidatorUnknown(validator_idx))?)
})
.collect::<Result<_>>()?;
let domain = spec.get_domain(
indexed_attestation.data.target.epoch,
Domain::BeaconAttester,
&fork,
genesis_validators_root,
);
let message = indexed_attestation.data.signing_root(domain);
let message = message.as_bytes().to_vec();
Ok(SignatureSet::new(signature, pubkeys, message))
}
/// Returns the signature set for the given `attester_slashing` and corresponding `pubkeys`.
pub fn attester_slashing_signature_sets<'a, T, F>(
state: &'a BeaconState<T>,
get_pubkey: F,
attester_slashing: &'a AttesterSlashing<T>,
spec: &'a ChainSpec,
) -> Result<(SignatureSet, SignatureSet)>
where
T: EthSpec,
F: Fn(usize) -> Option<Cow<'a, PublicKey>> + Clone,
{
Ok((
indexed_attestation_signature_set(
state,
get_pubkey.clone(),
&attester_slashing.attestation_1.signature,
&attester_slashing.attestation_1,
spec,
)?,
indexed_attestation_signature_set(
state,
get_pubkey,
&attester_slashing.attestation_2.signature,
&attester_slashing.attestation_2,
spec,
)?,
))
}
/// Returns the BLS values in a `Deposit`, if they're all valid. Otherwise, returns `None`.
///
/// This method is separate to `deposit_signature_set` to satisfy lifetime requirements.
pub fn deposit_pubkey_signature_message(
deposit_data: &DepositData,
spec: &ChainSpec,
) -> Option<(PublicKey, Signature, Vec<u8>)> {
let pubkey = (&deposit_data.pubkey).try_into().ok()?;
let signature = (&deposit_data.signature).try_into().ok()?;
let domain = spec.get_deposit_domain();
let message = deposit_data
.as_deposit_message()
.signing_root(domain)
.as_bytes()
.to_vec();
Some((pubkey, signature, message))
}
/// Returns the signature set for some set of deposit signatures, made with
/// `deposit_pubkey_signature_message`.
pub fn deposit_signature_set<'a>(
pubkey_signature_message: &'a (PublicKey, Signature, Vec<u8>),
) -> SignatureSet {
let (pubkey, signature, message) = pubkey_signature_message;
// Note: Deposits are valid across forks, thus the deposit domain is computed
// with the fok zeroed.
SignatureSet::single(&signature, Cow::Borrowed(pubkey), message.clone())
}
/// Returns a signature set that is valid if the `SignedVoluntaryExit` was signed by the indicated
/// validator.
pub fn exit_signature_set<'a, T, F>(
state: &'a BeaconState<T>,
get_pubkey: F,
signed_exit: &'a SignedVoluntaryExit,
spec: &'a ChainSpec,
) -> Result<SignatureSet>
where
T: EthSpec,
F: Fn(usize) -> Option<Cow<'a, PublicKey>>,
{
let exit = &signed_exit.message;
let proposer_index = exit.validator_index as usize;
let domain = spec.get_domain(
exit.epoch,
Domain::VoluntaryExit,
&state.fork,
state.genesis_validators_root,
);
let message = exit.signing_root(domain).as_bytes().to_vec();
Ok(SignatureSet::single(
&signed_exit.signature,
get_pubkey(proposer_index).ok_or_else(|| Error::ValidatorUnknown(proposer_index as u64))?,
message,
))
}
pub fn signed_aggregate_selection_proof_signature_set<'a, T, F>(
get_pubkey: F,
signed_aggregate_and_proof: &'a SignedAggregateAndProof<T>,
fork: &Fork,
genesis_validators_root: Hash256,
spec: &'a ChainSpec,
) -> Result<SignatureSet>
where
T: EthSpec,
F: Fn(usize) -> Option<Cow<'a, PublicKey>>,
{
let slot = signed_aggregate_and_proof.message.aggregate.data.slot;
let domain = spec.get_domain(
slot.epoch(T::slots_per_epoch()),
Domain::SelectionProof,
fork,
genesis_validators_root,
);
let message = slot.signing_root(domain).as_bytes().to_vec();
let signature = &signed_aggregate_and_proof.message.selection_proof;
let validator_index = signed_aggregate_and_proof.message.aggregator_index;
Ok(SignatureSet::single(
signature,
get_pubkey(validator_index as usize)
.ok_or_else(|| Error::ValidatorUnknown(validator_index))?,
message,
))
}
pub fn signed_aggregate_signature_set<'a, T, F>(
get_pubkey: F,
signed_aggregate_and_proof: &'a SignedAggregateAndProof<T>,
fork: &Fork,
genesis_validators_root: Hash256,
spec: &'a ChainSpec,
) -> Result<SignatureSet>
where
T: EthSpec,
F: Fn(usize) -> Option<Cow<'a, PublicKey>>,
{
let target_epoch = signed_aggregate_and_proof
.message
.aggregate
.data
.target
.epoch;
let domain = spec.get_domain(
target_epoch,
Domain::AggregateAndProof,
fork,
genesis_validators_root,
);
let message = signed_aggregate_and_proof
.message
.signing_root(domain)
.as_bytes()
.to_vec();
let signature = &signed_aggregate_and_proof.signature;
let validator_index = signed_aggregate_and_proof.message.aggregator_index;
Ok(SignatureSet::single(
signature,
get_pubkey(validator_index as usize)
.ok_or_else(|| Error::ValidatorUnknown(validator_index))?,
message,
))
}

View File

@@ -0,0 +1,896 @@
#![cfg(all(test, not(feature = "fake_crypto")))]
use super::block_processing_builder::BlockProcessingBuilder;
use super::errors::*;
use crate::{per_block_processing, BlockSignatureStrategy};
use types::test_utils::{
AttestationTestTask, AttesterSlashingTestTask, DepositTestTask, ProposerSlashingTestTask,
};
use types::*;
pub const NUM_DEPOSITS: u64 = 1;
pub const VALIDATOR_COUNT: usize = 64;
pub const EPOCH_OFFSET: u64 = 4;
pub const NUM_ATTESTATIONS: u64 = 1;
type E = MainnetEthSpec;
#[test]
fn valid_block_ok() {
let spec = MainnetEthSpec::default_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,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
assert_eq!(result, Ok(()));
}
#[test]
fn invalid_block_header_state_slot() {
let spec = MainnetEthSpec::default_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);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
assert_eq!(
result,
Err(BlockProcessingError::HeaderInvalid {
reason: HeaderInvalid::StateSlotMismatch
})
);
}
#[test]
fn invalid_parent_block_root() {
let spec = MainnetEthSpec::default_spec();
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));
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
assert_eq!(
result,
Err(BlockProcessingError::HeaderInvalid {
reason: HeaderInvalid::ParentBlockRootMismatch {
state: state.latest_block_header.canonical_root(),
block: block.parent_root()
}
})
);
}
#[test]
fn invalid_block_signature() {
let spec = MainnetEthSpec::default_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();
let block = block.message.sign(
&keypair.sk,
&state.fork,
state.genesis_validators_root,
&spec,
);
// process block with invalid block signature
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// should get a BadSignature error
assert_eq!(
result,
Err(BlockProcessingError::HeaderInvalid {
reason: HeaderInvalid::ProposalSignatureInvalid
})
);
}
#[test]
fn invalid_randao_reveal_signature() {
let spec = MainnetEthSpec::default_spec();
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);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// should get a BadRandaoSignature error
assert_eq!(result, Err(BlockProcessingError::RandaoSignatureInvalid));
}
#[test]
fn valid_4_deposits() {
let spec = MainnetEthSpec::default_spec();
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);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting Ok because these are valid deposits.
assert_eq!(result, Ok(()));
}
#[test]
fn invalid_deposit_deposit_count_too_big() {
let spec = MainnetEthSpec::default_spec();
let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT);
let test_task = DepositTestTask::Valid;
let (block, mut state) =
builder.build_with_n_deposits(NUM_DEPOSITS, test_task, None, None, &spec);
let big_deposit_count = NUM_DEPOSITS + 1;
state.eth1_data.deposit_count = big_deposit_count;
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting DepositCountInvalid because we incremented the deposit_count
assert_eq!(
result,
Err(BlockProcessingError::DepositCountInvalid {
expected: big_deposit_count as usize,
found: 1
})
);
}
#[test]
fn invalid_deposit_count_too_small() {
let spec = MainnetEthSpec::default_spec();
let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT);
let test_task = DepositTestTask::Valid;
let (block, mut state) =
builder.build_with_n_deposits(NUM_DEPOSITS, test_task, None, None, &spec);
let small_deposit_count = NUM_DEPOSITS - 1;
state.eth1_data.deposit_count = small_deposit_count;
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting DepositCountInvalid because we decremented the deposit_count
assert_eq!(
result,
Err(BlockProcessingError::DepositCountInvalid {
expected: small_deposit_count as usize,
found: 1
})
);
}
#[test]
fn invalid_deposit_bad_merkle_proof() {
let spec = MainnetEthSpec::default_spec();
let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT);
let test_task = DepositTestTask::Valid;
let (block, mut state) =
builder.build_with_n_deposits(NUM_DEPOSITS, test_task, None, None, &spec);
let bad_index = state.eth1_deposit_index as usize;
// Manually offsetting deposit count and index to trigger bad merkle proof
state.eth1_data.deposit_count += 1;
state.eth1_deposit_index += 1;
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting BadMerkleProof because the proofs were created with different indices
assert_eq!(
result,
Err(BlockProcessingError::DepositInvalid {
index: bad_index,
reason: DepositInvalid::BadMerkleProof
})
);
}
#[test]
fn invalid_deposit_wrong_pubkey() {
let spec = MainnetEthSpec::default_spec();
let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT);
let test_task = DepositTestTask::BadPubKey;
let (block, mut state) =
builder.build_with_n_deposits(NUM_DEPOSITS, test_task, None, None, &spec);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting Ok(()) even though the public key provided does not correspond to the correct public key
assert_eq!(result, Ok(()));
}
#[test]
fn invalid_deposit_wrong_sig() {
let spec = MainnetEthSpec::default_spec();
let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT);
let test_task = DepositTestTask::BadSig;
let (block, mut state) =
builder.build_with_n_deposits(NUM_DEPOSITS, test_task, None, None, &spec);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting Ok(()) even though the block signature does not correspond to the correct public key
assert_eq!(result, Ok(()));
}
#[test]
fn invalid_deposit_invalid_pub_key() {
let spec = MainnetEthSpec::default_spec();
let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT);
let test_task = DepositTestTask::InvalidPubKey;
let (block, mut state) =
builder.build_with_n_deposits(NUM_DEPOSITS, test_task, None, None, &spec);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting Ok(()) even though we passed in invalid publickeybytes in the public key field of the deposit data.
assert_eq!(result, Ok(()));
}
#[test]
fn valid_attestations() {
let spec = MainnetEthSpec::default_spec();
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);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting Ok(()) because these are valid attestations
assert_eq!(result, Ok(()));
}
#[test]
fn invalid_attestation_no_committee_for_index() {
let spec = MainnetEthSpec::default_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,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting NoCommitee because we manually set the attestation's index to be invalid
assert_eq!(
result,
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, 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);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting WrongJustifiedCheckpoint because we manually set the
// source field of the AttestationData object to be invalid
assert_eq!(
result,
Err(BlockProcessingError::AttestationInvalid {
index: 0,
reason: AttestationInvalid::WrongJustifiedCheckpoint {
state: Checkpoint {
epoch: Epoch::from(2 as u64),
root: Hash256::zero(),
},
attestation: Checkpoint {
epoch: Epoch::from(0 as u64),
root: Hash256::zero(),
},
is_current: true,
}
})
);
}
#[test]
fn invalid_attestation_bad_indexed_attestation_bad_signature() {
let spec = MainnetEthSpec::default_spec();
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);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting BadIndexedAttestation(BadSignature) because we ommitted the aggregation bits in the attestation
assert_eq!(
result,
Err(BlockProcessingError::AttestationInvalid {
index: 0,
reason: AttestationInvalid::BadIndexedAttestation(
IndexedAttestationInvalid::BadSignature
)
})
);
}
#[test]
fn invalid_attestation_bad_aggregation_bitfield_len() {
let spec = MainnetEthSpec::default_spec();
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);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting InvalidBitfield because the size of the aggregation_bitfield is bigger than the commitee size.
assert_eq!(
result,
Err(BlockProcessingError::BeaconStateError(
BeaconStateError::InvalidBitfield
))
);
}
#[test]
fn invalid_attestation_bad_signature() {
let spec = MainnetEthSpec::default_spec();
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);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting BadSignature because we're signing with invalid secret_keys
assert_eq!(
result,
Err(BlockProcessingError::AttestationInvalid {
index: 0,
reason: AttestationInvalid::BadIndexedAttestation(
IndexedAttestationInvalid::BadSignature
)
})
);
}
#[test]
fn invalid_attestation_included_too_early() {
let spec = MainnetEthSpec::default_spec();
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);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting IncludedTooEarly because the shard included in the crosslink is bigger than expected
assert_eq!(
result,
Err(BlockProcessingError::AttestationInvalid {
index: 0,
reason: AttestationInvalid::IncludedTooEarly {
state: state.slot,
delay: spec.min_attestation_inclusion_delay,
attestation: block.message.body.attestations[0].data.slot,
}
})
);
}
#[test]
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, 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);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
assert_eq!(
result,
Err(BlockProcessingError::AttestationInvalid {
index: 0,
reason: AttestationInvalid::IncludedTooLate {
state: state.slot,
attestation: block.message.body.attestations[0].data.slot,
}
})
);
}
#[test]
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, 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);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
let attestation = &block.message.body.attestations[0].data;
assert_eq!(
result,
Err(BlockProcessingError::AttestationInvalid {
index: 0,
reason: AttestationInvalid::TargetEpochSlotMismatch {
target_epoch: attestation.target.epoch,
slot_epoch: attestation.slot.epoch(E::slots_per_epoch()),
}
})
);
}
#[test]
fn valid_insert_attester_slashing() {
let spec = MainnetEthSpec::default_spec();
let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT);
let test_task = AttesterSlashingTestTask::Valid;
let num_attester_slashings = 1;
let (block, mut state) =
builder.build_with_attester_slashing(test_task, num_attester_slashings, None, None, &spec);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting Ok(()) because attester slashing is valid
assert_eq!(result, Ok(()));
}
#[test]
fn invalid_attester_slashing_not_slashable() {
let spec = MainnetEthSpec::default_spec();
let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT);
let test_task = AttesterSlashingTestTask::NotSlashable;
let num_attester_slashings = 1;
let (block, mut state) =
builder.build_with_attester_slashing(test_task, num_attester_slashings, None, None, &spec);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting NotSlashable because the two attestations are the same
assert_eq!(
result,
Err(BlockProcessingError::AttesterSlashingInvalid {
index: 0,
reason: AttesterSlashingInvalid::NotSlashable
})
);
}
#[test]
fn invalid_attester_slashing_1_invalid() {
let spec = MainnetEthSpec::default_spec();
let builder = get_builder(&spec, EPOCH_OFFSET, VALIDATOR_COUNT);
let test_task = AttesterSlashingTestTask::IndexedAttestation1Invalid;
let num_attester_slashings = 1;
let (block, mut state) =
builder.build_with_attester_slashing(test_task, num_attester_slashings, None, None, &spec);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
assert_eq!(
result,
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, EPOCH_OFFSET, VALIDATOR_COUNT);
let test_task = AttesterSlashingTestTask::IndexedAttestation2Invalid;
let num_attester_slashings = 1;
let (block, mut state) =
builder.build_with_attester_slashing(test_task, num_attester_slashings, None, None, &spec);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
assert_eq!(
result,
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, EPOCH_OFFSET, VALIDATOR_COUNT);
let test_task = ProposerSlashingTestTask::Valid;
let (block, mut state) = builder.build_with_proposer_slashing(test_task, 1, None, None, &spec);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting Ok(()) because we inserted a valid proposer slashing
assert_eq!(result, Ok(()));
}
#[test]
fn invalid_proposer_slashing_proposals_identical() {
let spec = MainnetEthSpec::default_spec();
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);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting ProposalsIdentical because we the two headers are identical
assert_eq!(
result,
Err(BlockProcessingError::ProposerSlashingInvalid {
index: 0,
reason: ProposerSlashingInvalid::ProposalsIdentical
})
);
}
#[test]
fn invalid_proposer_slashing_proposer_unknown() {
let spec = MainnetEthSpec::default_spec();
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);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting ProposerUnknown because validator_index is unknown
assert_eq!(
result,
Err(BlockProcessingError::ProposerSlashingInvalid {
index: 0,
reason: ProposerSlashingInvalid::ProposerUnknown(3_141_592)
})
);
}
#[test]
fn invalid_proposer_slashing_not_slashable() {
let spec = MainnetEthSpec::default_spec();
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);
state.validators[0].slashed = true;
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting ProposerNotSlashable because we've already slashed the validator
assert_eq!(
result,
Err(BlockProcessingError::ProposerSlashingInvalid {
index: 0,
reason: ProposerSlashingInvalid::ProposerNotSlashable(0)
})
);
}
#[test]
fn invalid_proposer_slashing_duplicate_slashing() {
let spec = MainnetEthSpec::default_spec();
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);
let slashing = block.message.body.proposer_slashings[0].clone();
let slashed_proposer = slashing.signed_header_1.message.proposer_index;
block
.message
.body
.proposer_slashings
.push(slashing)
.expect("should push slashing");
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::NoVerification,
&spec,
);
// Expecting ProposerNotSlashable for the 2nd slashing because the validator has been
// slashed by the 1st slashing.
assert_eq!(
result,
Err(BlockProcessingError::ProposerSlashingInvalid {
index: 1,
reason: ProposerSlashingInvalid::ProposerNotSlashable(slashed_proposer)
})
);
}
#[test]
fn invalid_bad_proposal_1_signature() {
let spec = MainnetEthSpec::default_spec();
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);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting BadProposal1Signature because signature of proposal 1 is invalid
assert_eq!(
result,
Err(BlockProcessingError::ProposerSlashingInvalid {
index: 0,
reason: ProposerSlashingInvalid::BadProposal1Signature
})
);
}
#[test]
fn invalid_bad_proposal_2_signature() {
let spec = MainnetEthSpec::default_spec();
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);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting BadProposal2Signature because signature of proposal 2 is invalid
assert_eq!(
result,
Err(BlockProcessingError::ProposerSlashingInvalid {
index: 0,
reason: ProposerSlashingInvalid::BadProposal2Signature
})
);
}
#[test]
fn invalid_proposer_slashing_proposal_epoch_mismatch() {
let spec = MainnetEthSpec::default_spec();
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);
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
&spec,
);
// Expecting ProposalEpochMismatch because the two epochs are different
assert_eq!(
result,
Err(BlockProcessingError::ProposerSlashingInvalid {
index: 0,
reason: ProposerSlashingInvalid::ProposalSlotMismatch(
Slot::from(0 as u64),
Slot::from(128 as u64)
)
})
);
}
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()
}

View File

@@ -0,0 +1,122 @@
use super::errors::{AttestationInvalid as Invalid, BlockOperationError};
use super::VerifySignatures;
use crate::common::get_indexed_attestation;
use crate::per_block_processing::is_valid_indexed_attestation;
use types::*;
type Result<T> = std::result::Result<T, BlockOperationError<Invalid>>;
fn error(reason: Invalid) -> BlockOperationError<Invalid> {
BlockOperationError::invalid(reason)
}
/// Returns `Ok(())` if the given `attestation` is valid to be included in a block that is applied
/// to `state`. Otherwise, returns a descriptive `Err`.
///
/// Optionally verifies the aggregate signature, depending on `verify_signatures`.
///
/// Spec v0.11.1
pub fn verify_attestation_for_block_inclusion<T: EthSpec>(
state: &BeaconState<T>,
attestation: &Attestation<T>,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<()> {
let data = &attestation.data;
verify!(
data.slot + spec.min_attestation_inclusion_delay <= state.slot,
Invalid::IncludedTooEarly {
state: state.slot,
delay: spec.min_attestation_inclusion_delay,
attestation: data.slot,
}
);
verify!(
state.slot <= data.slot + T::slots_per_epoch(),
Invalid::IncludedTooLate {
state: state.slot,
attestation: data.slot,
}
);
verify_attestation_for_state(state, attestation, verify_signatures, spec)
}
/// Returns `Ok(())` if `attestation` is a valid attestation to the chain that precedes the given
/// `state`.
///
/// Returns a descriptive `Err` if the attestation is malformed or does not accurately reflect the
/// prior blocks in `state`.
///
/// Spec v0.11.1
pub fn verify_attestation_for_state<T: EthSpec>(
state: &BeaconState<T>,
attestation: &Attestation<T>,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<()> {
let data = &attestation.data;
// This emptiness check is required *in addition* to the length check in `get_attesting_indices`
// because we can parse a bitfield and know its length, even if it has no bits set.
verify!(
!attestation.aggregation_bits.is_zero(),
Invalid::AggregationBitfieldIsEmpty
);
verify!(
data.index < state.get_committee_count_at_slot(data.slot)?,
Invalid::BadCommitteeIndex
);
// Verify the Casper FFG vote.
verify_casper_ffg_vote(attestation, state)?;
// Check signature and bitfields
let committee = state.get_beacon_committee(attestation.data.slot, attestation.data.index)?;
let indexed_attestation = get_indexed_attestation(committee.committee, attestation)?;
is_valid_indexed_attestation(state, &indexed_attestation, verify_signatures, spec)?;
Ok(())
}
/// Check target epoch and source checkpoint.
///
/// Spec v0.11.1
fn verify_casper_ffg_vote<T: EthSpec>(
attestation: &Attestation<T>,
state: &BeaconState<T>,
) -> Result<()> {
let data = &attestation.data;
verify!(
data.target.epoch == data.slot.epoch(T::slots_per_epoch()),
Invalid::TargetEpochSlotMismatch {
target_epoch: data.target.epoch,
slot_epoch: data.slot.epoch(T::slots_per_epoch()),
}
);
if data.target.epoch == state.current_epoch() {
verify!(
data.source == state.current_justified_checkpoint,
Invalid::WrongJustifiedCheckpoint {
state: state.current_justified_checkpoint.clone(),
attestation: data.source.clone(),
is_current: true,
}
);
Ok(())
} else if data.target.epoch == state.previous_epoch() {
verify!(
data.source == state.previous_justified_checkpoint,
Invalid::WrongJustifiedCheckpoint {
state: state.previous_justified_checkpoint.clone(),
attestation: data.source.clone(),
is_current: false,
}
);
Ok(())
} else {
Err(error(Invalid::BadTargetEpoch))
}
}

View File

@@ -0,0 +1,98 @@
use super::errors::{AttesterSlashingInvalid as Invalid, BlockOperationError};
use super::is_valid_indexed_attestation::is_valid_indexed_attestation;
use crate::per_block_processing::VerifySignatures;
use std::collections::BTreeSet;
use types::*;
type Result<T> = std::result::Result<T, BlockOperationError<Invalid>>;
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.
///
/// 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>,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<()> {
let attestation_1 = &attester_slashing.attestation_1;
let attestation_2 = &attester_slashing.attestation_2;
// Spec: is_slashable_attestation_data
verify!(
attestation_1.is_double_vote(attestation_2)
|| attestation_1.is_surround_vote(attestation_2),
Invalid::NotSlashable
);
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(())
}
/// For a given attester slashing, return the indices able to be slashed in ascending order.
///
/// Returns Ok(indices) if `indices.len() > 0`.
///
/// Spec v0.11.1
pub fn get_slashable_indices<T: EthSpec>(
state: &BeaconState<T>,
attester_slashing: &AttesterSlashing<T>,
) -> Result<Vec<u64>> {
get_slashable_indices_modular(state, attester_slashing, |_, validator| {
validator.is_slashable_at(state.current_epoch())
})
}
/// Same as `gather_attester_slashing_indices` but allows the caller to specify the criteria
/// for determining whether a given validator should be considered slashable.
pub fn get_slashable_indices_modular<F, T: EthSpec>(
state: &BeaconState<T>,
attester_slashing: &AttesterSlashing<T>,
is_slashable: F,
) -> Result<Vec<u64>>
where
F: Fn(u64, &Validator) -> bool,
{
let attestation_1 = &attester_slashing.attestation_1;
let attestation_2 = &attester_slashing.attestation_2;
let attesting_indices_1 = attestation_1
.attesting_indices
.iter()
.cloned()
.collect::<BTreeSet<_>>();
let attesting_indices_2 = attestation_2
.attesting_indices
.iter()
.cloned()
.collect::<BTreeSet<_>>();
let mut slashable_indices = vec![];
for index in &attesting_indices_1 & &attesting_indices_2 {
let validator = state
.validators
.get(index as usize)
.ok_or_else(|| error(Invalid::UnknownValidator(index)))?;
if is_slashable(index, validator) {
slashable_indices.push(index);
}
}
verify!(!slashable_indices.is_empty(), Invalid::NoSlashableIndices);
Ok(slashable_indices)
}

View File

@@ -0,0 +1,71 @@
use super::errors::{BlockOperationError, DepositInvalid};
use crate::per_block_processing::signature_sets::{
deposit_pubkey_signature_message, deposit_signature_set,
};
use merkle_proof::verify_merkle_proof;
use safe_arith::SafeArith;
use tree_hash::TreeHash;
use types::*;
type Result<T> = std::result::Result<T, BlockOperationError<DepositInvalid>>;
fn error(reason: DepositInvalid) -> BlockOperationError<DepositInvalid> {
BlockOperationError::invalid(reason)
}
/// Verify `Deposit.pubkey` signed `Deposit.signature`.
///
/// Spec v0.11.1
pub fn verify_deposit_signature(deposit_data: &DepositData, spec: &ChainSpec) -> Result<()> {
let deposit_signature_message = deposit_pubkey_signature_message(&deposit_data, spec)
.ok_or_else(|| error(DepositInvalid::BadBlsBytes))?;
verify!(
deposit_signature_set(&deposit_signature_message).is_valid(),
DepositInvalid::BadSignature
);
Ok(())
}
/// Returns a `Some(validator index)` if a pubkey already exists in the `validators`,
/// otherwise returns `None`.
///
/// ## Errors
///
/// Errors if the state's `pubkey_cache` is not current.
pub fn get_existing_validator_index<T: EthSpec>(
state: &BeaconState<T>,
pub_key: &PublicKeyBytes,
) -> Result<Option<u64>> {
let validator_index = state.get_validator_index(pub_key)?;
Ok(validator_index.map(|idx| idx as u64))
}
/// Verify that a deposit is included in the state's eth1 deposit root.
///
/// The deposit index is provided as a parameter so we can check proofs
/// before they're due to be processed, and in parallel.
///
/// Spec v0.11.1
pub fn verify_deposit_merkle_proof<T: EthSpec>(
state: &BeaconState<T>,
deposit: &Deposit,
deposit_index: u64,
spec: &ChainSpec,
) -> Result<()> {
let leaf = deposit.data.tree_hash_root();
verify!(
verify_merkle_proof(
leaf,
&deposit.proof[..],
spec.deposit_contract_tree_depth.safe_add(1)? as usize,
deposit_index as usize,
state.eth1_data.deposit_root,
),
DepositInvalid::BadMerkleProof
);
Ok(())
}

View File

@@ -0,0 +1,102 @@
use super::errors::{BlockOperationError, ExitInvalid};
use crate::per_block_processing::{
signature_sets::{exit_signature_set, get_pubkey_from_state},
VerifySignatures,
};
use types::*;
type Result<T> = std::result::Result<T, BlockOperationError<ExitInvalid>>;
fn error(reason: ExitInvalid) -> BlockOperationError<ExitInvalid> {
BlockOperationError::invalid(reason)
}
/// Indicates if an `Exit` is valid to be included in a block in the current epoch of the given
/// state.
///
/// Returns `Ok(())` if the `Exit` is valid, otherwise indicates the reason for invalidity.
///
/// Spec v0.11.1
pub fn verify_exit<T: EthSpec>(
state: &BeaconState<T>,
exit: &SignedVoluntaryExit,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<()> {
verify_exit_parametric(state, exit, verify_signatures, spec, false)
}
/// Like `verify_exit` but doesn't run checks which may become true in future states.
///
/// Spec v0.11.1
pub fn verify_exit_time_independent_only<T: EthSpec>(
state: &BeaconState<T>,
exit: &SignedVoluntaryExit,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<()> {
verify_exit_parametric(state, exit, verify_signatures, spec, true)
}
/// Parametric version of `verify_exit` that skips some checks if `time_independent_only` is true.
///
/// Spec v0.11.1
fn verify_exit_parametric<T: EthSpec>(
state: &BeaconState<T>,
signed_exit: &SignedVoluntaryExit,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
time_independent_only: bool,
) -> Result<()> {
let exit = &signed_exit.message;
let validator = state
.validators
.get(exit.validator_index as usize)
.ok_or_else(|| error(ExitInvalid::ValidatorUnknown(exit.validator_index)))?;
// Verify the validator is active.
verify!(
validator.is_active_at(state.current_epoch()),
ExitInvalid::NotActive(exit.validator_index)
);
// Verify that the validator has not yet exited.
verify!(
validator.exit_epoch == spec.far_future_epoch,
ExitInvalid::AlreadyExited(exit.validator_index)
);
// Exits must specify an epoch when they become valid; they are not valid before then.
verify!(
time_independent_only || state.current_epoch() >= exit.epoch,
ExitInvalid::FutureEpoch {
state: state.current_epoch(),
exit: exit.epoch
}
);
// Verify the validator has been active long enough.
verify!(
state.current_epoch() >= validator.activation_epoch + spec.persistent_committee_period,
ExitInvalid::TooYoungToExit {
current_epoch: state.current_epoch(),
earliest_exit_epoch: validator.activation_epoch + spec.persistent_committee_period,
}
);
if verify_signatures.is_true() {
verify!(
exit_signature_set(
state,
|i| get_pubkey_from_state(state, i),
signed_exit,
spec
)?
.is_valid(),
ExitInvalid::BadSignature
);
}
Ok(())
}

View File

@@ -0,0 +1,65 @@
use super::errors::{BlockOperationError, ProposerSlashingInvalid as Invalid};
use super::signature_sets::{get_pubkey_from_state, proposer_slashing_signature_set};
use crate::VerifySignatures;
use types::*;
type Result<T> = std::result::Result<T, BlockOperationError<Invalid>>;
fn error(reason: Invalid) -> BlockOperationError<Invalid> {
BlockOperationError::invalid(reason)
}
/// Indicates if a `ProposerSlashing` is valid to be included in a block in the current epoch of the given
/// state.
///
/// Returns `Ok(())` if the `ProposerSlashing` is valid, otherwise indicates the reason for invalidity.
///
/// Spec v0.11.1
pub fn verify_proposer_slashing<T: EthSpec>(
proposer_slashing: &ProposerSlashing,
state: &BeaconState<T>,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<()> {
let header_1 = &proposer_slashing.signed_header_1.message;
let header_2 = &proposer_slashing.signed_header_2.message;
// Verify slots match
verify!(
header_1.slot == header_2.slot,
Invalid::ProposalSlotMismatch(header_1.slot, header_2.slot)
);
// Verify header proposer indices match
verify!(
header_1.proposer_index == header_2.proposer_index,
Invalid::ProposerIndexMismatch(header_1.proposer_index, header_2.proposer_index)
);
// But the headers are different
verify!(header_1 != header_2, Invalid::ProposalsIdentical);
// Check proposer is slashable
let proposer = state
.validators
.get(header_1.proposer_index as usize)
.ok_or_else(|| error(Invalid::ProposerUnknown(header_1.proposer_index)))?;
verify!(
proposer.is_slashable_at(state.current_epoch()),
Invalid::ProposerNotSlashable(header_1.proposer_index)
);
if verify_signatures.is_true() {
let (signature_set_1, signature_set_2) = proposer_slashing_signature_set(
state,
|i| get_pubkey_from_state(state, i),
proposer_slashing,
spec,
)?;
verify!(signature_set_1.is_valid(), Invalid::BadProposal1Signature);
verify!(signature_set_2.is_valid(), Invalid::BadProposal2Signature);
}
Ok(())
}

View File

@@ -0,0 +1,212 @@
use errors::EpochProcessingError as Error;
use safe_arith::SafeArith;
use tree_hash::TreeHash;
use types::*;
pub mod apply_rewards;
pub mod errors;
pub mod process_slashings;
pub mod registry_updates;
pub mod tests;
pub mod validator_statuses;
pub use apply_rewards::process_rewards_and_penalties;
pub use process_slashings::process_slashings;
pub use registry_updates::process_registry_updates;
pub use validator_statuses::{TotalBalances, ValidatorStatus, ValidatorStatuses};
/// Provides a summary of validator participation during the epoch.
pub struct EpochProcessingSummary {
pub total_balances: TotalBalances,
}
/// Performs per-epoch processing on some BeaconState.
///
/// Mutates the given `BeaconState`, returning early if an error is encountered. If an error is
/// returned, a state might be "half-processed" and therefore in an invalid state.
///
/// Spec v0.11.1
pub fn per_epoch_processing<T: EthSpec>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> Result<EpochProcessingSummary, Error> {
// Ensure the committee caches are built.
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
state.build_committee_cache(RelativeEpoch::Current, spec)?;
state.build_committee_cache(RelativeEpoch::Next, spec)?;
// Load the struct we use to assign validators into sets based on their participation.
//
// E.g., attestation in the previous epoch, attested to the head, etc.
let mut validator_statuses = ValidatorStatuses::new(state, spec)?;
validator_statuses.process_attestations(&state, spec)?;
// Justification and finalization.
process_justification_and_finalization(state, &validator_statuses.total_balances)?;
// Rewards and Penalties.
process_rewards_and_penalties(state, &mut validator_statuses, spec)?;
// Registry Updates.
process_registry_updates(state, spec)?;
// Slashings.
process_slashings(
state,
validator_statuses.total_balances.current_epoch(),
spec,
)?;
// Final updates.
process_final_updates(state, spec)?;
// Rotate the epoch caches to suit the epoch transition.
state.advance_caches();
Ok(EpochProcessingSummary {
total_balances: validator_statuses.total_balances,
})
}
/// Update the following fields on the `BeaconState`:
///
/// - `justification_bitfield`.
/// - `previous_justified_epoch`
/// - `previous_justified_root`
/// - `current_justified_epoch`
/// - `current_justified_root`
/// - `finalized_epoch`
/// - `finalized_root`
///
/// Spec v0.11.1
#[allow(clippy::if_same_then_else)] // For readability and consistency with spec.
pub fn process_justification_and_finalization<T: EthSpec>(
state: &mut BeaconState<T>,
total_balances: &TotalBalances,
) -> Result<(), Error> {
if state.current_epoch() <= T::genesis_epoch() + 1 {
return Ok(());
}
let previous_epoch = state.previous_epoch();
let current_epoch = state.current_epoch();
let old_previous_justified_checkpoint = state.previous_justified_checkpoint.clone();
let old_current_justified_checkpoint = state.current_justified_checkpoint.clone();
// Process justifications
state.previous_justified_checkpoint = state.current_justified_checkpoint.clone();
state.justification_bits.shift_up(1)?;
if total_balances
.previous_epoch_target_attesters()
.safe_mul(3)?
>= total_balances.current_epoch().safe_mul(2)?
{
state.current_justified_checkpoint = Checkpoint {
epoch: previous_epoch,
root: *state.get_block_root_at_epoch(previous_epoch)?,
};
state.justification_bits.set(1, true)?;
}
// If the current epoch gets justified, fill the last bit.
if total_balances
.current_epoch_target_attesters()
.safe_mul(3)?
>= total_balances.current_epoch().safe_mul(2)?
{
state.current_justified_checkpoint = Checkpoint {
epoch: current_epoch,
root: *state.get_block_root_at_epoch(current_epoch)?,
};
state.justification_bits.set(0, true)?;
}
let bits = &state.justification_bits;
// The 2nd/3rd/4th most recent epochs are all justified, the 2nd using the 4th as source.
if (1..4).all(|i| bits.get(i).unwrap_or(false))
&& old_previous_justified_checkpoint.epoch + 3 == current_epoch
{
state.finalized_checkpoint = old_previous_justified_checkpoint;
}
// The 2nd/3rd most recent epochs are both justified, the 2nd using the 3rd as source.
else if (1..3).all(|i| bits.get(i).unwrap_or(false))
&& old_previous_justified_checkpoint.epoch + 2 == current_epoch
{
state.finalized_checkpoint = old_previous_justified_checkpoint;
}
// The 1st/2nd/3rd most recent epochs are all justified, the 1st using the 3nd as source.
if (0..3).all(|i| bits.get(i).unwrap_or(false))
&& old_current_justified_checkpoint.epoch + 2 == current_epoch
{
state.finalized_checkpoint = old_current_justified_checkpoint;
}
// The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source.
else if (0..2).all(|i| bits.get(i).unwrap_or(false))
&& old_current_justified_checkpoint.epoch + 1 == current_epoch
{
state.finalized_checkpoint = old_current_justified_checkpoint;
}
Ok(())
}
/// Finish up an epoch update.
///
/// Spec v0.11.1
pub fn process_final_updates<T: EthSpec>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> Result<(), Error> {
let current_epoch = state.current_epoch();
let next_epoch = state.next_epoch();
// Reset eth1 data votes.
if (state.slot + 1) % T::SlotsPerEth1VotingPeriod::to_u64() == 0 {
state.eth1_data_votes = VariableList::empty();
}
// Update effective balances with hysteresis (lag).
let hysteresis_increment = spec
.effective_balance_increment
.safe_div(spec.hysteresis_quotient)?;
let downward_threshold = hysteresis_increment.safe_mul(spec.hysteresis_downward_multiplier)?;
let upward_threshold = hysteresis_increment.safe_mul(spec.hysteresis_upward_multiplier)?;
for (index, validator) in state.validators.iter_mut().enumerate() {
let balance = state.balances[index];
if balance.safe_add(downward_threshold)? < validator.effective_balance
|| validator.effective_balance.safe_add(upward_threshold)? < balance
{
validator.effective_balance = std::cmp::min(
balance.safe_sub(balance.safe_rem(spec.effective_balance_increment)?)?,
spec.max_effective_balance,
);
}
}
// Reset slashings
state.set_slashings(next_epoch, 0)?;
// Set randao mix
state.set_randao_mix(next_epoch, *state.get_randao_mix(current_epoch)?)?;
// Set historical root accumulator
if next_epoch
.as_u64()
.safe_rem(T::SlotsPerHistoricalRoot::to_u64().safe_div(T::slots_per_epoch())?)?
== 0
{
let historical_batch = state.historical_batch();
state
.historical_roots
.push(historical_batch.tree_hash_root())?;
}
// Rotate current/previous epoch attestations
state.previous_epoch_attestations =
std::mem::replace(&mut state.current_epoch_attestations, VariableList::empty());
Ok(())
}

View File

@@ -0,0 +1,244 @@
use super::super::common::get_base_reward;
use super::validator_statuses::{TotalBalances, ValidatorStatus, ValidatorStatuses};
use super::Error;
use safe_arith::SafeArith;
use types::*;
/// Use to track the changes to a validators balance.
#[derive(Default, Clone)]
pub struct Delta {
rewards: u64,
penalties: u64,
}
impl Delta {
/// Reward the validator with the `reward`.
pub fn reward(&mut self, reward: u64) -> Result<(), Error> {
self.rewards = self.rewards.safe_add(reward)?;
Ok(())
}
/// Penalize the validator with the `penalty`.
pub fn penalize(&mut self, penalty: u64) -> Result<(), Error> {
self.penalties = self.penalties.safe_add(penalty)?;
Ok(())
}
/// Combine two deltas.
fn combine(&mut self, other: Delta) -> Result<(), Error> {
self.reward(other.rewards)?;
self.penalize(other.penalties)
}
}
/// Apply attester and proposer rewards.
///
/// Spec v0.11.1
pub fn process_rewards_and_penalties<T: EthSpec>(
state: &mut BeaconState<T>,
validator_statuses: &mut ValidatorStatuses,
spec: &ChainSpec,
) -> Result<(), Error> {
if state.current_epoch() == T::genesis_epoch() {
return Ok(());
}
// Guard against an out-of-bounds during the validator balance update.
if validator_statuses.statuses.len() != state.balances.len()
|| validator_statuses.statuses.len() != state.validators.len()
{
return Err(Error::ValidatorStatusesInconsistent);
}
let mut deltas = vec![Delta::default(); state.balances.len()];
get_attestation_deltas(&mut deltas, state, &validator_statuses, spec)?;
get_proposer_deltas(&mut deltas, state, validator_statuses, spec)?;
// Apply the deltas, erroring on overflow above but not on overflow below (saturating at 0
// instead).
for (i, delta) in deltas.iter().enumerate() {
state.balances[i] = state.balances[i].safe_add(delta.rewards)?;
state.balances[i] = state.balances[i].saturating_sub(delta.penalties);
}
Ok(())
}
/// For each attesting validator, reward the proposer who was first to include their attestation.
///
/// Spec v0.11.1
fn get_proposer_deltas<T: EthSpec>(
deltas: &mut Vec<Delta>,
state: &BeaconState<T>,
validator_statuses: &mut ValidatorStatuses,
spec: &ChainSpec,
) -> Result<(), Error> {
for (index, validator) in validator_statuses.statuses.iter().enumerate() {
if validator.is_previous_epoch_attester && !validator.is_slashed {
let inclusion = validator
.inclusion_info
.expect("It is a logic error for an attester not to have an inclusion delay.");
let base_reward = get_base_reward(
state,
index,
validator_statuses.total_balances.current_epoch(),
spec,
)?;
if inclusion.proposer_index >= deltas.len() {
return Err(Error::ValidatorStatusesInconsistent);
}
deltas[inclusion.proposer_index]
.reward(base_reward.safe_div(spec.proposer_reward_quotient)?)?;
}
}
Ok(())
}
/// Apply rewards for participation in attestations during the previous epoch.
///
/// Spec v0.11.1
fn get_attestation_deltas<T: EthSpec>(
deltas: &mut Vec<Delta>,
state: &BeaconState<T>,
validator_statuses: &ValidatorStatuses,
spec: &ChainSpec,
) -> Result<(), Error> {
let finality_delay = (state.previous_epoch() - state.finalized_checkpoint.epoch).as_u64();
for (index, validator) in validator_statuses.statuses.iter().enumerate() {
let base_reward = get_base_reward(
state,
index,
validator_statuses.total_balances.current_epoch(),
spec,
)?;
let delta = get_attestation_delta::<T>(
&validator,
&validator_statuses.total_balances,
base_reward,
finality_delay,
spec,
)?;
deltas[index].combine(delta)?;
}
Ok(())
}
/// Determine the delta for a single validator, sans proposer rewards.
///
/// Spec v0.11.1
fn get_attestation_delta<T: EthSpec>(
validator: &ValidatorStatus,
total_balances: &TotalBalances,
base_reward: u64,
finality_delay: u64,
spec: &ChainSpec,
) -> Result<Delta, Error> {
let mut delta = Delta::default();
// Is this validator eligible to be rewarded or penalized?
// Spec: validator index in `eligible_validator_indices`
let is_eligible = validator.is_active_in_previous_epoch
|| (validator.is_slashed && !validator.is_withdrawable_in_current_epoch);
if !is_eligible {
return Ok(delta);
}
// Handle integer overflow by dividing these quantities by EFFECTIVE_BALANCE_INCREMENT
// Spec:
// - increment = EFFECTIVE_BALANCE_INCREMENT
// - reward_numerator = get_base_reward(state, index) * (attesting_balance // increment)
// - rewards[index] = reward_numerator // (total_balance // increment)
let total_balance_ebi = total_balances
.current_epoch()
.safe_div(spec.effective_balance_increment)?;
let total_attesting_balance_ebi = total_balances
.previous_epoch_attesters()
.safe_div(spec.effective_balance_increment)?;
let matching_target_balance_ebi = total_balances
.previous_epoch_target_attesters()
.safe_div(spec.effective_balance_increment)?;
let matching_head_balance_ebi = total_balances
.previous_epoch_head_attesters()
.safe_div(spec.effective_balance_increment)?;
// Expected FFG source.
// Spec:
// - validator index in `get_unslashed_attesting_indices(state, matching_source_attestations)`
if validator.is_previous_epoch_attester && !validator.is_slashed {
delta.reward(
base_reward
.safe_mul(total_attesting_balance_ebi)?
.safe_div(total_balance_ebi)?,
)?;
// Inclusion speed bonus
let proposer_reward = base_reward.safe_div(spec.proposer_reward_quotient)?;
let max_attester_reward = base_reward.safe_sub(proposer_reward)?;
let inclusion = validator
.inclusion_info
.expect("It is a logic error for an attester not to have an inclusion delay.");
delta.reward(max_attester_reward.safe_div(inclusion.delay)?)?;
} else {
delta.penalize(base_reward)?;
}
// Expected FFG target.
// Spec:
// - validator index in `get_unslashed_attesting_indices(state, matching_target_attestations)`
if validator.is_previous_epoch_target_attester && !validator.is_slashed {
delta.reward(
base_reward
.safe_mul(matching_target_balance_ebi)?
.safe_div(total_balance_ebi)?,
)?;
} else {
delta.penalize(base_reward)?;
}
// Expected head.
// Spec:
// - validator index in `get_unslashed_attesting_indices(state, matching_head_attestations)`
if validator.is_previous_epoch_head_attester && !validator.is_slashed {
delta.reward(
base_reward
.safe_mul(matching_head_balance_ebi)?
.safe_div(total_balance_ebi)?,
)?;
} else {
delta.penalize(base_reward)?;
}
// Inactivity penalty
if finality_delay > spec.min_epochs_to_inactivity_penalty {
// All eligible validators are penalized
delta.penalize(spec.base_rewards_per_epoch.safe_mul(base_reward)?)?;
// Additionally, all validators whose FFG target didn't match are penalized extra
if !validator.is_previous_epoch_target_attester {
delta.penalize(
validator
.current_epoch_effective_balance
.safe_mul(finality_delay)?
.safe_div(spec.inactivity_penalty_quotient)?,
)?;
}
}
// Proposer bonus is handled in `get_proposer_deltas`.
//
// This function only computes the delta for a single validator, so it cannot also return a
// delta for a validator.
Ok(delta)
}

View File

@@ -0,0 +1,59 @@
use types::*;
#[derive(Debug, PartialEq)]
pub enum EpochProcessingError {
UnableToDetermineProducer,
NoBlockRoots,
BaseRewardQuotientIsZero,
NoRandaoSeed,
PreviousTotalBalanceIsZero,
InclusionDistanceZero,
ValidatorStatusesInconsistent,
DeltasInconsistent,
/// Unable to get the inclusion distance for a validator that should have an inclusion
/// distance. This indicates an internal inconsistency.
///
/// (validator_index)
InclusionSlotsInconsistent(usize),
BeaconStateError(BeaconStateError),
InclusionError(InclusionError),
SszTypesError(ssz_types::Error),
ArithError(safe_arith::ArithError),
}
impl From<InclusionError> for EpochProcessingError {
fn from(e: InclusionError) -> EpochProcessingError {
EpochProcessingError::InclusionError(e)
}
}
impl From<BeaconStateError> for EpochProcessingError {
fn from(e: BeaconStateError) -> EpochProcessingError {
EpochProcessingError::BeaconStateError(e)
}
}
impl From<ssz_types::Error> for EpochProcessingError {
fn from(e: ssz_types::Error) -> EpochProcessingError {
EpochProcessingError::SszTypesError(e)
}
}
impl From<safe_arith::ArithError> for EpochProcessingError {
fn from(e: safe_arith::ArithError) -> EpochProcessingError {
EpochProcessingError::ArithError(e)
}
}
#[derive(Debug, PartialEq)]
pub enum InclusionError {
/// The validator did not participate in an attestation in this period.
NoAttestationsForValidator,
BeaconStateError(BeaconStateError),
}
impl From<BeaconStateError> for InclusionError {
fn from(e: BeaconStateError) -> InclusionError {
InclusionError::BeaconStateError(e)
}
}

View File

@@ -0,0 +1,35 @@
use safe_arith::SafeArith;
use types::{BeaconStateError as Error, *};
/// Process slashings.
///
/// Spec v0.11.1
pub fn process_slashings<T: EthSpec>(
state: &mut BeaconState<T>,
total_balance: u64,
spec: &ChainSpec,
) -> Result<(), Error> {
let epoch = state.current_epoch();
let sum_slashings = state.get_all_slashings().iter().sum::<u64>();
for (index, validator) in state.validators.iter().enumerate() {
if validator.slashed
&& epoch + T::EpochsPerSlashingsVector::to_u64().safe_div(2)?
== validator.withdrawable_epoch
{
let increment = spec.effective_balance_increment;
let penalty_numerator = validator
.effective_balance
.safe_div(increment)?
.safe_mul(std::cmp::min(sum_slashings.safe_mul(3)?, total_balance))?;
let penalty = penalty_numerator
.safe_div(total_balance)?
.safe_mul(increment)?;
// Equivalent to `decrease_balance(state, index, penalty)`, but avoids borrowing `state`.
state.balances[index] = state.balances[index].saturating_sub(penalty);
}
}
Ok(())
}

View File

@@ -0,0 +1,62 @@
use super::super::common::initiate_validator_exit;
use super::Error;
use itertools::{Either, Itertools};
use types::*;
/// Performs a validator registry update, if required.
///
/// Spec v0.11.1
pub fn process_registry_updates<T: EthSpec>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> Result<(), Error> {
// Process activation eligibility and ejections.
// Collect eligible and exiting validators (we need to avoid mutating the state while iterating).
// We assume it's safe to re-order the change in eligibility and `initiate_validator_exit`.
// Rest assured exiting validators will still be exited in the same order as in the spec.
let current_epoch = state.current_epoch();
let is_exiting_validator = |validator: &Validator| {
validator.is_active_at(current_epoch)
&& validator.effective_balance <= spec.ejection_balance
};
let (eligible_validators, exiting_validators): (Vec<_>, Vec<_>) = state
.validators
.iter()
.enumerate()
.filter(|(_, validator)| {
validator.is_eligible_for_activation_queue(spec) || is_exiting_validator(validator)
})
.partition_map(|(index, validator)| {
if validator.is_eligible_for_activation_queue(spec) {
Either::Left(index)
} else {
Either::Right(index)
}
});
for index in eligible_validators {
state.validators[index].activation_eligibility_epoch = current_epoch + 1;
}
for index in exiting_validators {
initiate_validator_exit(state, index, spec)?;
}
// Queue validators eligible for activation and not dequeued for activation prior to finalized epoch
let activation_queue = state
.validators
.iter()
.enumerate()
.filter(|(_, validator)| validator.is_eligible_for_activation(state, spec))
.sorted_by_key(|(index, validator)| (validator.activation_eligibility_epoch, *index))
.map(|(index, _)| index)
.collect_vec();
// Dequeue validators for activation up to churn limit
let churn_limit = state.get_churn_limit(spec)? as usize;
let delayed_activation_epoch = state.compute_activation_exit_epoch(current_epoch, spec);
for index in activation_queue.into_iter().take(churn_limit) {
let validator = &mut state.validators[index];
validator.activation_epoch = delayed_activation_epoch;
}
Ok(())
}

View File

@@ -0,0 +1,23 @@
#![cfg(test)]
use crate::per_epoch_processing::per_epoch_processing;
use env_logger::{Builder, Env};
use types::test_utils::TestingBeaconStateBuilder;
use types::*;
#[test]
fn runs_without_error() {
Builder::from_env(Env::default().default_filter_or("error")).init();
let spec = MinimalEthSpec::default_spec();
let mut builder: TestingBeaconStateBuilder<MinimalEthSpec> =
TestingBeaconStateBuilder::from_deterministic_keypairs(8, &spec);
let target_slot =
(MinimalEthSpec::genesis_epoch() + 4).end_slot(MinimalEthSpec::slots_per_epoch());
builder.teleport_to_slot(target_slot);
let (mut state, _keypairs) = builder.build();
per_epoch_processing(&mut state, &spec).unwrap();
}

View File

@@ -0,0 +1,348 @@
use crate::common::get_attesting_indices;
use safe_arith::SafeArith;
use types::*;
#[cfg(feature = "arbitrary-fuzz")]
use arbitrary::Arbitrary;
/// Sets the boolean `var` on `self` to be true if it is true on `other`. Otherwise leaves `self`
/// as is.
macro_rules! set_self_if_other_is_true {
($self_: ident, $other: ident, $var: ident) => {
if $other.$var {
$self_.$var = true;
}
};
}
/// The information required to reward a block producer for including an attestation in a block.
#[cfg_attr(feature = "arbitrary-fuzz", derive(Arbitrary))]
#[derive(Debug, Clone, Copy)]
pub struct InclusionInfo {
/// The distance between the attestation slot and the slot that attestation was included in a
/// block.
pub delay: u64,
/// The index of the proposer at the slot where the attestation was included.
pub proposer_index: usize,
}
impl Default for InclusionInfo {
/// Defaults to `delay` at its maximum value and `proposer_index` at zero.
fn default() -> Self {
Self {
delay: u64::max_value(),
proposer_index: 0,
}
}
}
impl InclusionInfo {
/// Tests if some `other` `InclusionInfo` has a lower inclusion slot than `self`. If so,
/// replaces `self` with `other`.
pub fn update(&mut self, other: &Self) {
if other.delay < self.delay {
self.delay = other.delay;
self.proposer_index = other.proposer_index;
}
}
}
/// Information required to reward some validator during the current and previous epoch.
#[cfg_attr(feature = "arbitrary-fuzz", derive(Arbitrary))]
#[derive(Debug, Default, Clone)]
pub struct ValidatorStatus {
/// True if the validator has been slashed, ever.
pub is_slashed: bool,
/// True if the validator can withdraw in the current epoch.
pub is_withdrawable_in_current_epoch: bool,
/// True if the validator was active in the state's _current_ epoch.
pub is_active_in_current_epoch: bool,
/// True if the validator was active in the state's _previous_ epoch.
pub is_active_in_previous_epoch: bool,
/// The validator's effective balance in the _current_ epoch.
pub current_epoch_effective_balance: u64,
/// True if the validator had an attestation included in the _current_ epoch.
pub is_current_epoch_attester: bool,
/// True if the validator's beacon block root attestation for the first slot of the _current_
/// epoch matches the block root known to the state.
pub is_current_epoch_target_attester: bool,
/// True if the validator had an attestation included in the _previous_ epoch.
pub is_previous_epoch_attester: bool,
/// True if the validator's beacon block root attestation for the first slot of the _previous_
/// epoch matches the block root known to the state.
pub is_previous_epoch_target_attester: bool,
/// True if the validator's beacon block root attestation in the _previous_ epoch at the
/// attestation's slot (`attestation_data.slot`) matches the block root known to the state.
pub is_previous_epoch_head_attester: bool,
/// Information used to reward the block producer of this validators earliest-included
/// attestation.
pub inclusion_info: Option<InclusionInfo>,
}
impl ValidatorStatus {
/// Accepts some `other` `ValidatorStatus` and updates `self` if required.
///
/// Will never set one of the `bool` fields to `false`, it will only set it to `true` if other
/// contains a `true` field.
///
/// Note: does not update the winning root info, this is done manually.
pub fn update(&mut self, other: &Self) {
// Update all the bool fields, only updating `self` if `other` is true (never setting
// `self` to false).
set_self_if_other_is_true!(self, other, is_slashed);
set_self_if_other_is_true!(self, other, is_withdrawable_in_current_epoch);
set_self_if_other_is_true!(self, other, is_active_in_current_epoch);
set_self_if_other_is_true!(self, other, is_active_in_previous_epoch);
set_self_if_other_is_true!(self, other, is_current_epoch_attester);
set_self_if_other_is_true!(self, other, is_current_epoch_target_attester);
set_self_if_other_is_true!(self, other, is_previous_epoch_attester);
set_self_if_other_is_true!(self, other, is_previous_epoch_target_attester);
set_self_if_other_is_true!(self, other, is_previous_epoch_head_attester);
if let Some(other_info) = other.inclusion_info {
if let Some(self_info) = self.inclusion_info.as_mut() {
self_info.update(&other_info);
} else {
self.inclusion_info = other.inclusion_info;
}
}
}
}
/// The total effective balances for different sets of validators during the previous and current
/// epochs.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary-fuzz", derive(Arbitrary))]
pub struct TotalBalances {
/// The effective balance increment from the spec.
effective_balance_increment: u64,
/// The total effective balance of all active validators during the _current_ epoch.
current_epoch: u64,
/// The total effective balance of all active validators during the _previous_ epoch.
previous_epoch: u64,
/// The total effective balance of all validators who attested during the _current_ epoch.
current_epoch_attesters: u64,
/// The total effective balance of all validators who attested during the _current_ epoch and
/// agreed with the state about the beacon block at the first slot of the _current_ epoch.
current_epoch_target_attesters: u64,
/// The total effective balance of all validators who attested during the _previous_ epoch.
previous_epoch_attesters: u64,
/// The total effective balance of all validators who attested during the _previous_ epoch and
/// agreed with the state about the beacon block at the first slot of the _previous_ epoch.
previous_epoch_target_attesters: u64,
/// The total effective balance of all validators who attested during the _previous_ epoch and
/// agreed with the state about the beacon block at the time of attestation.
previous_epoch_head_attesters: u64,
}
// Generate a safe accessor for a balance in `TotalBalances`, as per spec `get_total_balance`.
macro_rules! balance_accessor {
($field_name:ident) => {
pub fn $field_name(&self) -> u64 {
std::cmp::max(self.effective_balance_increment, self.$field_name)
}
};
}
impl TotalBalances {
pub fn new(spec: &ChainSpec) -> Self {
Self {
effective_balance_increment: spec.effective_balance_increment,
current_epoch: 0,
previous_epoch: 0,
current_epoch_attesters: 0,
current_epoch_target_attesters: 0,
previous_epoch_attesters: 0,
previous_epoch_target_attesters: 0,
previous_epoch_head_attesters: 0,
}
}
balance_accessor!(current_epoch);
balance_accessor!(previous_epoch);
balance_accessor!(current_epoch_attesters);
balance_accessor!(current_epoch_target_attesters);
balance_accessor!(previous_epoch_attesters);
balance_accessor!(previous_epoch_target_attesters);
balance_accessor!(previous_epoch_head_attesters);
}
/// Summarised information about validator participation in the _previous and _current_ epochs of
/// some `BeaconState`.
#[cfg_attr(feature = "arbitrary-fuzz", derive(Arbitrary))]
#[derive(Debug, Clone)]
pub struct ValidatorStatuses {
/// Information about each individual validator from the state's validator registry.
pub statuses: Vec<ValidatorStatus>,
/// Summed balances for various sets of validators.
pub total_balances: TotalBalances,
}
impl ValidatorStatuses {
/// Initializes a new instance, determining:
///
/// - Active validators
/// - Total balances for the current and previous epochs.
///
/// Spec v0.11.1
pub fn new<T: EthSpec>(
state: &BeaconState<T>,
spec: &ChainSpec,
) -> Result<Self, BeaconStateError> {
let mut statuses = Vec::with_capacity(state.validators.len());
let mut total_balances = TotalBalances::new(spec);
for (i, validator) in state.validators.iter().enumerate() {
let effective_balance = state.get_effective_balance(i, spec)?;
let mut status = ValidatorStatus {
is_slashed: validator.slashed,
is_withdrawable_in_current_epoch: validator
.is_withdrawable_at(state.current_epoch()),
current_epoch_effective_balance: effective_balance,
..ValidatorStatus::default()
};
if validator.is_active_at(state.current_epoch()) {
status.is_active_in_current_epoch = true;
total_balances
.current_epoch
.safe_add_assign(effective_balance)?;
}
if validator.is_active_at(state.previous_epoch()) {
status.is_active_in_previous_epoch = true;
total_balances
.previous_epoch
.safe_add_assign(effective_balance)?;
}
statuses.push(status);
}
Ok(Self {
statuses,
total_balances,
})
}
/// Process some attestations from the given `state` updating the `statuses` and
/// `total_balances` fields.
///
/// Spec v0.11.1
pub fn process_attestations<T: EthSpec>(
&mut self,
state: &BeaconState<T>,
spec: &ChainSpec,
) -> Result<(), BeaconStateError> {
for a in state
.previous_epoch_attestations
.iter()
.chain(state.current_epoch_attestations.iter())
{
let committee = state.get_beacon_committee(a.data.slot, a.data.index)?;
let attesting_indices =
get_attesting_indices::<T>(committee.committee, &a.aggregation_bits)?;
let mut status = ValidatorStatus::default();
// Profile this attestation, updating the total balances and generating an
// `ValidatorStatus` object that applies to all participants in the attestation.
if a.data.target.epoch == state.current_epoch() {
status.is_current_epoch_attester = true;
if target_matches_epoch_start_block(a, state, state.current_epoch())? {
status.is_current_epoch_target_attester = true;
}
} else if a.data.target.epoch == state.previous_epoch() {
status.is_previous_epoch_attester = true;
// The inclusion delay and proposer index are only required for previous epoch
// attesters.
status.inclusion_info = Some(InclusionInfo {
delay: a.inclusion_delay,
proposer_index: a.proposer_index as usize,
});
if target_matches_epoch_start_block(a, state, state.previous_epoch())? {
status.is_previous_epoch_target_attester = true;
if has_common_beacon_block_root(a, state)? {
status.is_previous_epoch_head_attester = true;
}
}
}
// Loop through the participating validator indices and update the status vec.
for validator_index in attesting_indices {
self.statuses[validator_index].update(&status);
}
}
// Compute the total balances
for (index, v) in self.statuses.iter().enumerate() {
// According to the spec, we only count unslashed validators towards the totals.
if !v.is_slashed {
let validator_balance = state.get_effective_balance(index, spec)?;
if v.is_current_epoch_attester {
self.total_balances
.current_epoch_attesters
.safe_add_assign(validator_balance)?;
}
if v.is_current_epoch_target_attester {
self.total_balances
.current_epoch_target_attesters
.safe_add_assign(validator_balance)?;
}
if v.is_previous_epoch_attester {
self.total_balances
.previous_epoch_attesters
.safe_add_assign(validator_balance)?;
}
if v.is_previous_epoch_target_attester {
self.total_balances
.previous_epoch_target_attesters
.safe_add_assign(validator_balance)?;
}
if v.is_previous_epoch_head_attester {
self.total_balances
.previous_epoch_head_attesters
.safe_add_assign(validator_balance)?;
}
}
}
Ok(())
}
}
/// Returns `true` if the attestation's FFG target is equal to the hash of the `state`'s first
/// beacon block in the given `epoch`.
///
/// Spec v0.11.1
fn target_matches_epoch_start_block<T: EthSpec>(
a: &PendingAttestation<T>,
state: &BeaconState<T>,
epoch: Epoch,
) -> Result<bool, BeaconStateError> {
let slot = epoch.start_slot(T::slots_per_epoch());
let state_boundary_root = *state.get_block_root(slot)?;
Ok(a.data.target.root == state_boundary_root)
}
/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for
/// the current slot of the `PendingAttestation`.
///
/// Spec v0.11.1
fn has_common_beacon_block_root<T: EthSpec>(
a: &PendingAttestation<T>,
state: &BeaconState<T>,
) -> Result<bool, BeaconStateError> {
let state_block_root = *state.get_block_root(a.data.slot)?;
Ok(a.data.beacon_block_root == state_block_root)
}

View File

@@ -0,0 +1,80 @@
use crate::{per_epoch_processing::EpochProcessingSummary, *};
use types::*;
#[derive(Debug, PartialEq)]
pub enum Error {
BeaconStateError(BeaconStateError),
EpochProcessingError(EpochProcessingError),
}
/// Advances a state forward by one slot, performing per-epoch processing if required.
///
/// If the root of the supplied `state` is known, then it can be passed as `state_root`. If
/// `state_root` is `None`, the root of `state` will be computed using a cached tree hash.
/// Providing the `state_root` makes this function several orders of magniude faster.
///
/// Spec v0.11.1
pub fn per_slot_processing<T: EthSpec>(
state: &mut BeaconState<T>,
state_root: Option<Hash256>,
spec: &ChainSpec,
) -> Result<Option<EpochProcessingSummary>, Error> {
cache_state(state, state_root)?;
let mut summary = None;
if state.slot > spec.genesis_slot && (state.slot + 1) % T::slots_per_epoch() == 0 {
summary = Some(per_epoch_processing(state, spec)?);
}
state.slot += 1;
Ok(summary)
}
fn cache_state<T: EthSpec>(
state: &mut BeaconState<T>,
state_root: Option<Hash256>,
) -> Result<(), Error> {
let previous_state_root = if let Some(root) = state_root {
root
} else {
state.update_tree_hash_cache()?
};
// Note: increment the state slot here to allow use of our `state_root` and `block_root`
// getter/setter functions.
//
// This is a bit hacky, however it gets the job safely without lots of code.
let previous_slot = state.slot;
state.slot += 1;
// Store the previous slot's post state transition root.
state.set_state_root(previous_slot, previous_state_root)?;
// Cache latest block header state root
if state.latest_block_header.state_root == Hash256::zero() {
state.latest_block_header.state_root = previous_state_root;
}
// Cache block root
let latest_block_root = state.latest_block_header.canonical_root();
state.set_block_root(previous_slot, latest_block_root)?;
// Set the state slot back to what it should be.
state.slot -= 1;
Ok(())
}
impl From<BeaconStateError> for Error {
fn from(e: BeaconStateError) -> Error {
Error::BeaconStateError(e)
}
}
impl From<EpochProcessingError> for Error {
fn from(e: EpochProcessingError) -> Error {
Error::EpochProcessingError(e)
}
}

View File

@@ -0,0 +1,184 @@
use log::info;
use types::test_utils::{
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>,
pub num_validators: usize,
pub num_proposer_slashings: usize,
pub num_attester_slashings: usize,
pub num_attestations: usize,
pub num_deposits: usize,
pub num_exits: usize,
}
impl<T: EthSpec> BlockBuilder<T> {
pub fn new(num_validators: usize, spec: &ChainSpec) -> Self {
let state_builder =
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(num_validators, &spec);
let block_builder = TestingBeaconBlockBuilder::new(spec);
Self {
state_builder,
block_builder,
num_validators: 0,
num_proposer_slashings: 0,
num_attester_slashings: 0,
num_attestations: 0,
num_deposits: 0,
num_exits: 0,
}
}
pub fn maximize_block_operations(&mut self) {
self.num_proposer_slashings = T::MaxProposerSlashings::to_usize();
self.num_attester_slashings = T::MaxAttesterSlashings::to_usize();
self.num_attestations = T::MaxAttestations::to_usize();
self.num_deposits = T::MaxDeposits::to_usize();
self.num_exits = T::MaxVoluntaryExits::to_usize();
}
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(mut self, 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);
let proposer_index = state.get_beacon_proposer_index(state.slot, spec).unwrap();
let proposer_keypair = &keypairs[proposer_index];
builder.set_proposer_index(proposer_index as u64);
builder.set_randao_reveal(
&proposer_keypair.sk,
&state.fork,
state.genesis_validators_root,
spec,
);
let parent_root = state.latest_block_header.canonical_root();
builder.set_parent_root(parent_root);
// Used as a stream of validator indices for use in slashings, exits, etc.
let mut validators_iter = 0..keypairs.len() as u64;
// Insert `ProposerSlashing` objects.
for _ in 0..self.num_proposer_slashings {
let validator_index = validators_iter.next().expect("Insufficient validators.");
builder.insert_proposer_slashing(
ProposerSlashingTestTask::Valid,
validator_index,
&keypairs[validator_index as usize].sk,
&state.fork,
state.genesis_validators_root,
spec,
);
}
info!(
"Inserted {} proposer slashings.",
builder.block.body.proposer_slashings.len()
);
// Insert `AttesterSlashing` objects
for _ in 0..self.num_attester_slashings {
let mut attesters: Vec<u64> = vec![];
let mut secret_keys: Vec<&SecretKey> = vec![];
const NUM_SLASHED_INDICES: usize = 12;
for _ in 0..NUM_SLASHED_INDICES {
let validator_index = validators_iter.next().expect("Insufficient validators.");
attesters.push(validator_index);
secret_keys.push(&keypairs[validator_index as usize].sk);
}
builder.insert_attester_slashing(
AttesterSlashingTestTask::Valid,
&attesters,
&secret_keys,
&state.fork,
state.genesis_validators_root,
spec,
);
}
info!(
"Inserted {} attester slashings.",
builder.block.body.attester_slashings.len()
);
// Insert `Attestation` objects.
let all_secret_keys: Vec<&SecretKey> = keypairs.iter().map(|keypair| &keypair.sk).collect();
builder
.insert_attestations(
AttestationTestTask::Valid,
&state,
&all_secret_keys,
self.num_attestations as usize,
spec,
)
.unwrap();
info!(
"Inserted {} attestations.",
builder.block.body.attestations.len()
);
// Insert `Deposit` objects.
builder.insert_deposits(
32_000_000_000,
DepositTestTask::NoReset,
state.eth1_data.deposit_count,
self.num_deposits as u64,
&mut state,
spec,
);
info!("Inserted {} deposits.", builder.block.body.deposits.len());
// Insert the maximum possible number of `Exit` objects.
for _ in 0..self.num_exits {
let validator_index = validators_iter.next().expect("Insufficient validators.");
builder.insert_exit(
validator_index,
state.current_epoch(),
&keypairs[validator_index as usize].sk,
&state,
spec,
);
}
info!(
"Inserted {} exits.",
builder.block.body.voluntary_exits.len()
);
// Set the eth1 data to be different from the state.
self.block_builder.block.body.eth1_data.block_hash = Hash256::from_slice(&[42; 32]);
let block = self.block_builder.build(
&proposer_keypair.sk,
&state.fork,
state.genesis_validators_root,
spec,
);
(block, state)
}
}

View File

@@ -0,0 +1,228 @@
#![cfg(not(feature = "fake_crypto"))]
use state_processing::{
per_block_processing, test_utils::BlockBuilder, BlockProcessingError, BlockSignatureStrategy,
};
use types::{
AggregateSignature, BeaconState, ChainSpec, EthSpec, Keypair, MinimalEthSpec, Signature,
SignedBeaconBlock, Slot,
};
const VALIDATOR_COUNT: usize = 64;
fn get_block<T, F>(mut mutate_builder: F) -> (SignedBeaconBlock<T>, BeaconState<T>)
where
T: EthSpec,
F: FnMut(&mut BlockBuilder<T>),
{
let spec = T::default_spec();
let mut builder: BlockBuilder<T> = BlockBuilder::new(VALIDATOR_COUNT, &spec);
builder.set_slot(Slot::from(T::slots_per_epoch() * 3 - 2));
builder.build_caches(&spec);
mutate_builder(&mut builder);
builder.build(&spec)
}
fn test_scenario<T: EthSpec, F, G>(mutate_builder: F, mut invalidate_block: G, spec: &ChainSpec)
where
T: EthSpec,
F: FnMut(&mut BlockBuilder<T>),
G: FnMut(&mut SignedBeaconBlock<T>),
{
let (mut block, mut state) = get_block::<T, _>(mutate_builder);
/*
* Control check to ensure the valid block should pass verification.
*/
assert_eq!(
per_block_processing(
&mut state.clone(),
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
spec
),
Ok(()),
"valid block should pass with verify individual"
);
assert_eq!(
per_block_processing(
&mut state.clone(),
&block,
None,
BlockSignatureStrategy::VerifyBulk,
spec
),
Ok(()),
"valid block should pass with verify bulk"
);
invalidate_block(&mut block);
/*
* Check to ensure the invalid block fails.
*/
assert!(
per_block_processing(
&mut state.clone(),
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
spec
)
.is_err(),
"invalid block should fail with verify individual"
);
assert_eq!(
per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyBulk,
spec
),
Err(BlockProcessingError::BulkSignatureVerificationFailed),
"invalid block should fail with verify bulk"
);
}
// TODO: use lazy static
fn agg_sig() -> AggregateSignature {
let mut agg_sig = AggregateSignature::new();
agg_sig.add(&sig());
agg_sig
}
// TODO: use lazy static
fn sig() -> Signature {
let keypair = Keypair::random();
Signature::new(&[42, 42], &keypair.sk)
}
type TestEthSpec = MinimalEthSpec;
mod signatures_minimal {
use super::*;
#[test]
fn block_proposal() {
let spec = &TestEthSpec::default_spec();
test_scenario::<TestEthSpec, _, _>(|_| {}, |block| block.signature = sig(), spec);
}
#[test]
fn randao() {
let spec = &TestEthSpec::default_spec();
test_scenario::<TestEthSpec, _, _>(
|_| {},
|block| block.message.body.randao_reveal = sig(),
spec,
);
}
#[test]
fn proposer_slashing() {
let spec = &TestEthSpec::default_spec();
test_scenario::<TestEthSpec, _, _>(
|mut builder| {
builder.num_proposer_slashings = 1;
},
|block| {
block.message.body.proposer_slashings[0]
.signed_header_1
.signature = sig()
},
spec,
);
test_scenario::<TestEthSpec, _, _>(
|mut builder| {
builder.num_proposer_slashings = 1;
},
|block| {
block.message.body.proposer_slashings[0]
.signed_header_2
.signature = sig()
},
spec,
);
}
#[test]
fn attester_slashing() {
let spec = &TestEthSpec::default_spec();
test_scenario::<TestEthSpec, _, _>(
|mut builder| {
builder.num_attester_slashings = 1;
},
|block| {
block.message.body.attester_slashings[0]
.attestation_1
.signature = agg_sig()
},
spec,
);
test_scenario::<TestEthSpec, _, _>(
|mut builder| {
builder.num_attester_slashings = 1;
},
|block| {
block.message.body.attester_slashings[0]
.attestation_2
.signature = agg_sig()
},
spec,
);
}
#[test]
fn attestation() {
let spec = &TestEthSpec::default_spec();
test_scenario::<TestEthSpec, _, _>(
|mut builder| {
builder.num_attestations = 1;
},
|block| block.message.body.attestations[0].signature = agg_sig(),
spec,
);
}
#[test]
// TODO: fix fail by making valid merkle proofs.
#[should_panic]
fn deposit() {
let spec = &TestEthSpec::default_spec();
test_scenario::<TestEthSpec, _, _>(
|mut builder| {
builder.num_deposits = 1;
},
|block| block.message.body.deposits[0].data.signature = sig().into(),
spec,
);
}
#[test]
fn exit() {
let mut spec = &mut TestEthSpec::default_spec();
// Allows the test to pass.
spec.persistent_committee_period = 0;
test_scenario::<TestEthSpec, _, _>(
|mut builder| {
builder.num_exits = 1;
},
|block| block.message.body.voluntary_exits[0].signature = sig(),
spec,
);
}
}