Merge pull request #259 from sigp/bench-epoch-trans

Add benching and test for epoch processing
This commit is contained in:
Age Manning 2019-02-26 16:43:24 +11:00 committed by GitHub
commit d140563545
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 295 additions and 84 deletions

View File

@ -4,6 +4,15 @@ version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"] authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018" edition = "2018"
[[bench]]
name = "benches"
harness = false
[dev-dependencies]
criterion = "0.2"
test_harness = { path = "../../beacon_node/beacon_chain/test_harness" }
env_logger = "0.6.0"
[dependencies] [dependencies]
hashing = { path = "../utils/hashing" } hashing = { path = "../utils/hashing" }
int_to_bytes = { path = "../utils/int_to_bytes" } int_to_bytes = { path = "../utils/int_to_bytes" }

View File

@ -0,0 +1,28 @@
use criterion::Criterion;
use criterion::{black_box, criterion_group, criterion_main};
// use env_logger::{Builder, Env};
use state_processing::SlotProcessable;
use types::{beacon_state::BeaconStateBuilder, ChainSpec, Hash256};
fn epoch_processing(c: &mut Criterion) {
// Builder::from_env(Env::default().default_filter_or("debug")).init();
let mut builder = BeaconStateBuilder::with_random_validators(8);
builder.spec = ChainSpec::few_validators();
builder.genesis().unwrap();
builder.teleport_to_end_of_epoch(builder.spec.genesis_epoch + 4);
let state = builder.build().unwrap();
c.bench_function("epoch processing", move |b| {
let spec = &builder.spec;
b.iter_with_setup(
|| state.clone(),
|mut state| black_box(state.per_slot_processing(Hash256::zero(), spec).unwrap()),
)
});
}
criterion_group!(benches, epoch_processing,);
criterion_main!(benches);

View File

@ -9,6 +9,8 @@ use types::{
Crosslink, Epoch, Hash256, InclusionError, PendingAttestation, RelativeEpoch, Crosslink, Epoch, Hash256, InclusionError, PendingAttestation, RelativeEpoch,
}; };
mod tests;
macro_rules! safe_add_assign { macro_rules! safe_add_assign {
($a: expr, $b: expr) => { ($a: expr, $b: expr) => {
$a = $a.saturating_add($b); $a = $a.saturating_add($b);
@ -725,11 +727,3 @@ impl From<BeaconStateError> for WinningRootError {
WinningRootError::BeaconStateError(e) WinningRootError::BeaconStateError(e)
} }
} }
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

View File

@ -0,0 +1,21 @@
#![cfg(test)]
use crate::EpochProcessable;
use env_logger::{Builder, Env};
use types::beacon_state::BeaconStateBuilder;
use types::*;
#[test]
fn runs_without_error() {
Builder::from_env(Env::default().default_filter_or("error")).init();
let mut builder = BeaconStateBuilder::with_random_validators(8);
builder.spec = ChainSpec::few_validators();
builder.genesis().unwrap();
builder.teleport_to_end_of_epoch(builder.spec.genesis_epoch + 4);
let mut state = builder.build().unwrap();
let spec = &builder.spec;
state.per_epoch_processing(spec).unwrap();
}

View File

@ -7,13 +7,16 @@ use crate::{
}; };
use bls::verify_proof_of_possession; use bls::verify_proof_of_possession;
use honey_badger_split::SplitExt; use honey_badger_split::SplitExt;
use log::{debug, trace}; use log::{debug, error, trace};
use rand::RngCore; use rand::RngCore;
use serde_derive::Serialize; use serde_derive::Serialize;
use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
use std::collections::HashMap; use std::collections::HashMap;
use swap_or_not_shuffle::get_permutated_index; use swap_or_not_shuffle::get_permutated_index;
pub use builder::BeaconStateBuilder;
mod builder;
mod epoch_cache; mod epoch_cache;
mod tests; mod tests;
@ -384,12 +387,13 @@ impl BeaconState {
seed: Hash256, seed: Hash256,
epoch: Epoch, epoch: Epoch,
spec: &ChainSpec, spec: &ChainSpec,
) -> Option<Vec<Vec<usize>>> { ) -> Result<Vec<Vec<usize>>, Error> {
let active_validator_indices = let active_validator_indices =
get_active_validator_indices(&self.validator_registry, epoch); get_active_validator_indices(&self.validator_registry, epoch);
if active_validator_indices.is_empty() { if active_validator_indices.is_empty() {
return None; error!("get_shuffling: no validators.");
return Err(Error::InsufficientValidators);
} }
let committees_per_epoch = let committees_per_epoch =
@ -402,22 +406,21 @@ impl BeaconState {
); );
let mut shuffled_active_validator_indices = vec![0; active_validator_indices.len()]; let mut shuffled_active_validator_indices = vec![0; active_validator_indices.len()];
for &i in &active_validator_indices { for (i, _) in active_validator_indices.iter().enumerate() {
let shuffled_i = get_permutated_index( let shuffled_i = get_permutated_index(
i, i,
active_validator_indices.len(), active_validator_indices.len(),
&seed[..], &seed[..],
spec.shuffle_round_count, spec.shuffle_round_count,
)?; )
.ok_or_else(|| Error::UnableToShuffle)?;
shuffled_active_validator_indices[i] = active_validator_indices[shuffled_i] shuffled_active_validator_indices[i] = active_validator_indices[shuffled_i]
} }
Some( Ok(shuffled_active_validator_indices
shuffled_active_validator_indices
.honey_badger_split(committees_per_epoch as usize) .honey_badger_split(committees_per_epoch as usize)
.map(|slice: &[usize]| slice.to_vec()) .map(|slice: &[usize]| slice.to_vec())
.collect(), .collect())
)
} }
/// Return the number of committees in the previous epoch. /// Return the number of committees in the previous epoch.
@ -522,7 +525,6 @@ impl BeaconState {
self.get_committee_params_at_slot(slot, registry_change, spec)?; self.get_committee_params_at_slot(slot, registry_change, spec)?;
self.get_shuffling(seed, shuffling_epoch, spec) self.get_shuffling(seed, shuffling_epoch, spec)
.ok_or_else(|| Error::UnableToShuffle)
} }
/// Returns the following params for the given slot: /// Returns the following params for the given slot:

View File

@ -0,0 +1,215 @@
use crate::*;
use bls::create_proof_of_possession;
/// Builds a `BeaconState` for use in testing or benchmarking.
///
/// Building the `BeaconState` is a three step processes:
///
/// 1. Create a new `BeaconStateBuilder`.
/// 2. Run the `genesis` function to generate a new BeaconState.
/// 3. (Optional) Use builder functions to modify the `BeaconState`.
/// 4. Call `build()` to obtain a cloned `BeaconState`.
///
/// Step (2) is necessary because step (3) requires an existing `BeaconState` object. (2) is not
/// included in (1) to allow for modifying params before generating the `BeaconState` (e.g., the
/// spec).
///
/// Step (4) produces a clone of the BeaconState and doesn't consume the `BeaconStateBuilder` to
/// allow access to the `keypairs` and `spec`.
pub struct BeaconStateBuilder {
pub state: Option<BeaconState>,
pub genesis_time: u64,
pub initial_validator_deposits: Vec<Deposit>,
pub latest_eth1_data: Eth1Data,
pub spec: ChainSpec,
pub keypairs: Vec<Keypair>,
}
impl BeaconStateBuilder {
/// Create a new builder with the given number of validators.
pub fn with_random_validators(validator_count: usize) -> Self {
let genesis_time = 10_000_000;
let keypairs: Vec<Keypair> = (0..validator_count)
.collect::<Vec<usize>>()
.iter()
.map(|_| Keypair::random())
.collect();
let initial_validator_deposits = keypairs
.iter()
.map(|keypair| Deposit {
branch: vec![], // branch verification is not specified.
index: 0, // index verification is not specified.
deposit_data: DepositData {
amount: 32_000_000_000, // 32 ETH (in Gwei)
timestamp: genesis_time - 1,
deposit_input: DepositInput {
pubkey: keypair.pk.clone(),
withdrawal_credentials: Hash256::zero(), // Withdrawal not possible.
proof_of_possession: create_proof_of_possession(&keypair),
},
},
})
.collect();
let latest_eth1_data = Eth1Data {
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
};
let spec = ChainSpec::foundation();
Self {
state: None,
genesis_time,
initial_validator_deposits,
latest_eth1_data,
spec,
keypairs,
}
}
/// Runs the `BeaconState::genesis` function and produces a `BeaconState`.
pub fn genesis(&mut self) -> Result<(), BeaconStateError> {
let state = BeaconState::genesis(
self.genesis_time,
self.initial_validator_deposits.clone(),
self.latest_eth1_data.clone(),
&self.spec,
)?;
self.state = Some(state);
Ok(())
}
/// Sets the `BeaconState` to be in the last slot of the given epoch.
///
/// Sets all justification/finalization parameters to be be as "perfect" as possible (i.e.,
/// highest justified and finalized slots, full justification bitfield, etc).
pub fn teleport_to_end_of_epoch(&mut self, epoch: Epoch) {
let state = self.state.as_mut().expect("Genesis required");
let slot = epoch.end_slot(self.spec.epoch_length);
state.slot = slot;
state.validator_registry_update_epoch = epoch - 1;
state.previous_calculation_epoch = epoch - 1;
state.current_calculation_epoch = epoch;
state.previous_epoch_seed = Hash256::from(&b"previous_seed"[..]);
state.current_epoch_seed = Hash256::from(&b"current_seed"[..]);
state.previous_justified_epoch = epoch - 2;
state.justified_epoch = epoch - 1;
state.justification_bitfield = u64::max_value();
state.finalized_epoch = epoch - 1;
}
/// Creates a full set of attestations for the `BeaconState`. Each attestation has full
/// participation from its committee and references the expected beacon_block hashes.
///
/// These attestations should be fully conducive to justification and finalization.
pub fn insert_attestations(&mut self) {
let state = self.state.as_mut().expect("Genesis required");
state
.build_epoch_cache(RelativeEpoch::Previous, &self.spec)
.unwrap();
state
.build_epoch_cache(RelativeEpoch::Current, &self.spec)
.unwrap();
let current_epoch = state.current_epoch(&self.spec);
let previous_epoch = state.previous_epoch(&self.spec);
let current_epoch_depth =
(state.slot - current_epoch.end_slot(self.spec.epoch_length)).as_usize();
let previous_epoch_slots = previous_epoch.slot_iter(self.spec.epoch_length);
let current_epoch_slots = current_epoch
.slot_iter(self.spec.epoch_length)
.take(current_epoch_depth);
for slot in previous_epoch_slots.chain(current_epoch_slots) {
let committees = state
.get_crosslink_committees_at_slot(slot, &self.spec)
.unwrap()
.clone();
for (committee, shard) in committees {
state
.latest_attestations
.push(committee_to_pending_attestation(
state, &committee, shard, slot, &self.spec,
))
}
}
}
/// Returns a cloned `BeaconState`.
pub fn build(&self) -> Result<BeaconState, BeaconStateError> {
match &self.state {
Some(state) => Ok(state.clone()),
None => panic!("Genesis required"),
}
}
}
/// Builds a valid PendingAttestation with full participation for some committee.
fn committee_to_pending_attestation(
state: &BeaconState,
committee: &[usize],
shard: u64,
slot: Slot,
spec: &ChainSpec,
) -> PendingAttestation {
let current_epoch = state.current_epoch(spec);
let previous_epoch = state.previous_epoch(spec);
let mut aggregation_bitfield = Bitfield::new();
let mut custody_bitfield = Bitfield::new();
for (i, _) in committee.iter().enumerate() {
aggregation_bitfield.set(i, true);
custody_bitfield.set(i, true);
}
let is_previous_epoch = state.slot.epoch(spec.epoch_length) != slot.epoch(spec.epoch_length);
let justified_epoch = if is_previous_epoch {
state.previous_justified_epoch
} else {
state.justified_epoch
};
let epoch_boundary_root = if is_previous_epoch {
*state
.get_block_root(previous_epoch.start_slot(spec.epoch_length), spec)
.unwrap()
} else {
*state
.get_block_root(current_epoch.start_slot(spec.epoch_length), spec)
.unwrap()
};
let justified_block_root = *state
.get_block_root(justified_epoch.start_slot(spec.epoch_length), &spec)
.unwrap();
PendingAttestation {
aggregation_bitfield,
data: AttestationData {
slot,
shard,
beacon_block_root: *state.get_block_root(slot, spec).unwrap(),
epoch_boundary_root,
shard_block_root: Hash256::zero(),
latest_crosslink: Crosslink {
epoch: slot.epoch(spec.epoch_length),
shard_block_root: Hash256::zero(),
},
justified_epoch,
justified_block_root,
},
custody_bitfield,
inclusion_slot: slot,
}
}

View File

@ -2,73 +2,13 @@
use super::*; use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use crate::{ use crate::{BeaconState, ChainSpec};
BeaconState, BeaconStateError, ChainSpec, Deposit, DepositData, DepositInput, Eth1Data,
Hash256, Keypair,
};
use bls::create_proof_of_possession;
use ssz::{ssz_encode, Decodable}; use ssz::{ssz_encode, Decodable};
struct BeaconStateTestBuilder {
pub genesis_time: u64,
pub initial_validator_deposits: Vec<Deposit>,
pub latest_eth1_data: Eth1Data,
pub spec: ChainSpec,
pub keypairs: Vec<Keypair>,
}
impl BeaconStateTestBuilder {
pub fn with_random_validators(validator_count: usize) -> Self {
let genesis_time = 10_000_000;
let keypairs: Vec<Keypair> = (0..validator_count)
.collect::<Vec<usize>>()
.iter()
.map(|_| Keypair::random())
.collect();
let initial_validator_deposits = keypairs
.iter()
.map(|keypair| Deposit {
branch: vec![], // branch verification is not specified.
index: 0, // index verification is not specified.
deposit_data: DepositData {
amount: 32_000_000_000, // 32 ETH (in Gwei)
timestamp: genesis_time - 1,
deposit_input: DepositInput {
pubkey: keypair.pk.clone(),
withdrawal_credentials: Hash256::zero(), // Withdrawal not possible.
proof_of_possession: create_proof_of_possession(&keypair),
},
},
})
.collect();
let latest_eth1_data = Eth1Data {
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
};
let spec = ChainSpec::foundation();
Self {
genesis_time,
initial_validator_deposits,
latest_eth1_data,
spec,
keypairs,
}
}
pub fn build(&self) -> Result<BeaconState, BeaconStateError> {
BeaconState::genesis(
self.genesis_time,
self.initial_validator_deposits.clone(),
self.latest_eth1_data.clone(),
&self.spec,
)
}
}
#[test] #[test]
pub fn can_produce_genesis_block() { pub fn can_produce_genesis_block() {
let builder = BeaconStateTestBuilder::with_random_validators(2); let mut builder = BeaconStateBuilder::with_random_validators(2);
builder.genesis().unwrap();
builder.build().unwrap(); builder.build().unwrap();
} }
@ -79,9 +19,11 @@ pub fn can_produce_genesis_block() {
pub fn get_attestation_participants_consistency() { pub fn get_attestation_participants_consistency() {
let mut rng = XorShiftRng::from_seed([42; 16]); let mut rng = XorShiftRng::from_seed([42; 16]);
let mut builder = BeaconStateTestBuilder::with_random_validators(8); let mut builder = BeaconStateBuilder::with_random_validators(8);
builder.spec = ChainSpec::few_validators(); builder.spec = ChainSpec::few_validators();
builder.genesis().unwrap();
let mut state = builder.build().unwrap(); let mut state = builder.build().unwrap();
let spec = builder.spec.clone(); let spec = builder.spec.clone();