Move benching_utils structs into types

This commit is contained in:
Paul Hauner
2019-03-11 11:17:27 +11:00
parent 6ae99a1462
commit 0b7082e2b9
12 changed files with 25 additions and 54 deletions

View File

@@ -6,7 +6,7 @@ use ssz::TreeHash;
/// Builds a `BeaconState` for use in production.
///
/// This struct should not be modified for use in testing scenarios. Use `TestingBeaconStateBuilder` for that purpose.
/// This struct should _not_ be modified for use in testing scenarios. Use `TestingBeaconStateBuilder` for that purpose.
///
/// This struct should remain safe and sensible for production usage.
pub struct BeaconStateBuilder {

View File

@@ -1,29 +1,20 @@
#![cfg(test)]
use super::*;
use crate::test_utils::TestingBeaconStateBuilder;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use crate::{BeaconState, ChainSpec};
use ssz::{ssz_encode, Decodable};
#[test]
pub fn can_produce_genesis_block() {
let mut builder = BeaconStateBuilder::new(2);
builder.build().unwrap();
}
/// Tests that `get_attestation_participants` is consistent with the result of
/// get_crosslink_committees_at_slot` with a full bitfield.
#[test]
pub fn get_attestation_participants_consistency() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let mut builder = BeaconStateBuilder::new(8);
builder.spec = ChainSpec::few_validators();
builder.build().unwrap();
let mut state = builder.cloned_state();
let spec = builder.spec.clone();
let spec = ChainSpec::few_validators();
let builder = TestingBeaconStateBuilder::new(8, &spec);
let (mut state, _keypairs) = builder.build();
state
.build_epoch_cache(RelativeEpoch::Previous, &spec)

View File

@@ -1,5 +1,7 @@
mod test_random;
mod testing_attestation_builder;
mod testing_beacon_block_builder;
mod testing_beacon_state_builder;
mod testing_deposit_builder;
mod testing_transfer_builder;
mod testing_voluntary_exit_builder;
@@ -7,6 +9,8 @@ mod testing_voluntary_exit_builder;
pub use rand::{prng::XorShiftRng, SeedableRng};
pub use test_random::TestRandom;
pub use testing_attestation_builder::TestingAttestationBuilder;
pub use testing_beacon_block_builder::TestingBeaconBlockBuilder;
pub use testing_beacon_state_builder::TestingBeaconStateBuilder;
pub use testing_deposit_builder::TestingDepositBuilder;
pub use testing_transfer_builder::TestingTransferBuilder;
pub use testing_voluntary_exit_builder::TestingVoluntaryExitBuilder;

View File

@@ -0,0 +1,251 @@
use crate::{
attester_slashing::AttesterSlashingBuilder,
proposer_slashing::ProposerSlashingBuilder,
test_utils::{
TestingAttestationBuilder, TestingDepositBuilder, TestingTransferBuilder,
TestingVoluntaryExitBuilder,
},
*,
};
use rayon::prelude::*;
use ssz::{SignedRoot, TreeHash};
pub struct TestingBeaconBlockBuilder {
block: BeaconBlock,
}
impl TestingBeaconBlockBuilder {
pub fn new(spec: &ChainSpec) -> Self {
Self {
block: BeaconBlock::genesis(spec.zero_hash, spec),
}
}
pub fn set_slot(&mut self, slot: Slot) {
self.block.slot = slot;
}
/// Signs the block.
pub fn sign(&mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) {
let proposal = self.block.proposal(spec);
let message = proposal.signed_root();
let epoch = self.block.slot.epoch(spec.slots_per_epoch);
let domain = spec.get_domain(epoch, Domain::Proposal, fork);
self.block.signature = Signature::new(&message, domain, sk);
}
/// Sets the randao to be a signature across the blocks epoch.
pub fn set_randao_reveal(&mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) {
let epoch = self.block.slot.epoch(spec.slots_per_epoch);
let message = epoch.hash_tree_root();
let domain = spec.get_domain(epoch, Domain::Randao, fork);
self.block.randao_reveal = Signature::new(&message, domain, sk);
}
/// Inserts a signed, valid `ProposerSlashing` for the validator.
pub fn insert_proposer_slashing(
&mut self,
validator_index: u64,
secret_key: &SecretKey,
fork: &Fork,
spec: &ChainSpec,
) {
let proposer_slashing = build_proposer_slashing(validator_index, secret_key, fork, spec);
self.block.body.proposer_slashings.push(proposer_slashing);
}
/// Inserts a signed, valid `AttesterSlashing` for each validator index in `validator_indices`.
pub fn insert_attester_slashing(
&mut self,
validator_indices: &[u64],
secret_keys: &[&SecretKey],
fork: &Fork,
spec: &ChainSpec,
) {
let attester_slashing =
build_double_vote_attester_slashing(validator_indices, secret_keys, fork, spec);
self.block.body.attester_slashings.push(attester_slashing);
}
/// Fills the block with as many attestations as possible.
///
/// Note: this will not perform well when `jepoch_committees_count % slots_per_epoch != 0`
pub fn fill_with_attestations(
&mut self,
state: &BeaconState,
secret_keys: &[&SecretKey],
spec: &ChainSpec,
) -> Result<(), BeaconStateError> {
let mut slot = self.block.slot - spec.min_attestation_inclusion_delay;
let mut attestations_added = 0;
// Stores the following (in order):
//
// - The slot of the committee.
// - A list of all validators in the committee.
// - A list of all validators in the committee that should sign the attestation.
// - The shard of the committee.
let mut committees: Vec<(Slot, Vec<usize>, Vec<usize>, u64)> = vec![];
// Loop backwards through slots gathering each committee, until:
//
// - The slot is too old to be included in a block at this slot.
// - The `MAX_ATTESTATIONS`.
loop {
if state.slot >= slot + spec.slots_per_epoch {
break;
}
for (committee, shard) in state.get_crosslink_committees_at_slot(slot, spec)? {
if attestations_added >= spec.max_attestations {
break;
}
committees.push((slot, committee.clone(), committee.clone(), *shard));
attestations_added += 1;
}
slot -= 1;
}
// Loop through all the committees, splitting each one in half until we have
// `MAX_ATTESTATIONS` committees.
loop {
if committees.len() >= spec.max_attestations as usize {
break;
}
for index in 0..committees.len() {
if committees.len() >= spec.max_attestations as usize {
break;
}
let (slot, committee, mut signing_validators, shard) = committees[index].clone();
let new_signing_validators =
signing_validators.split_off(signing_validators.len() / 2);
committees[index] = (slot, committee.clone(), signing_validators, shard);
committees.push((slot, committee, new_signing_validators, shard));
}
}
let mut attestations: Vec<Attestation> = committees
.par_iter()
.map(|(slot, committee, signing_validators, shard)| {
let mut builder =
TestingAttestationBuilder::new(state, committee, *slot, *shard, spec);
let signing_secret_keys: Vec<&SecretKey> = signing_validators
.iter()
.map(|validator_index| secret_keys[*validator_index])
.collect();
builder.sign(signing_validators, &signing_secret_keys, &state.fork, spec);
builder.build()
})
.collect();
self.block.body.attestations.append(&mut attestations);
Ok(())
}
/// Insert a `Valid` deposit into the state.
pub fn insert_deposit(&mut self, amount: u64, index: u64, spec: &ChainSpec) {
let keypair = Keypair::random();
let mut builder = TestingDepositBuilder::new(amount);
builder.set_index(index);
builder.sign(&keypair, spec);
self.block.body.deposits.push(builder.build())
}
/// Insert a `Valid` exit into the state.
pub fn insert_exit(
&mut self,
state: &BeaconState,
validator_index: u64,
secret_key: &SecretKey,
spec: &ChainSpec,
) {
let mut builder = TestingVoluntaryExitBuilder::new(
state.slot.epoch(spec.slots_per_epoch),
validator_index,
);
builder.sign(secret_key, &state.fork, spec);
self.block.body.voluntary_exits.push(builder.build())
}
/// Insert a `Valid` transfer into the state.
///
/// Note: this will set the validator to be withdrawable by directly modifying the state
/// validator registry. This _may_ cause problems historic hashes, etc.
pub fn insert_transfer(
&mut self,
state: &BeaconState,
from: u64,
to: u64,
amount: u64,
keypair: Keypair,
spec: &ChainSpec,
) {
let mut builder = TestingTransferBuilder::new(from, to, amount, state.slot);
builder.sign(keypair, &state.fork, spec);
self.block.body.transfers.push(builder.build())
}
/// Signs and returns the block, consuming the builder.
pub fn build(mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) -> BeaconBlock {
self.sign(sk, fork, spec);
self.block
}
/// Returns the block, consuming the builder.
pub fn build_without_signing(self) -> BeaconBlock {
self.block
}
}
/// Builds an `ProposerSlashing` for some `validator_index`.
///
/// Signs the message using a `BeaconChainHarness`.
fn build_proposer_slashing(
validator_index: u64,
secret_key: &SecretKey,
fork: &Fork,
spec: &ChainSpec,
) -> ProposerSlashing {
let signer = |_validator_index: u64, message: &[u8], epoch: Epoch, domain: Domain| {
let domain = spec.get_domain(epoch, domain, fork);
Signature::new(message, domain, secret_key)
};
ProposerSlashingBuilder::double_vote(validator_index, signer, spec)
}
/// Builds an `AttesterSlashing` for some `validator_indices`.
///
/// Signs the message using a `BeaconChainHarness`.
fn build_double_vote_attester_slashing(
validator_indices: &[u64],
secret_keys: &[&SecretKey],
fork: &Fork,
spec: &ChainSpec,
) -> AttesterSlashing {
let signer = |validator_index: u64, message: &[u8], epoch: Epoch, domain: Domain| {
let key_index = validator_indices
.iter()
.position(|&i| i == validator_index)
.expect("Unable to find attester slashing key");
let domain = spec.get_domain(epoch, domain, fork);
Signature::new(message, domain, secret_keys[key_index])
};
AttesterSlashingBuilder::double_vote(validator_indices, signer)
}

View File

@@ -0,0 +1,214 @@
use crate::beacon_state::BeaconStateBuilder;
use crate::*;
use bls::get_withdrawal_credentials;
use int_to_bytes::int_to_bytes48;
use rayon::prelude::*;
pub struct TestingBeaconStateBuilder {
state: BeaconState,
keypairs: Vec<Keypair>,
}
impl TestingBeaconStateBuilder {
pub fn new(validator_count: usize, spec: &ChainSpec) -> Self {
let keypairs: Vec<Keypair> = (0..validator_count)
.collect::<Vec<usize>>()
.par_iter()
.map(|&i| {
let secret = int_to_bytes48(i as u64 + 1);
let sk = SecretKey::from_bytes(&secret).unwrap();
let pk = PublicKey::from_secret_key(&sk);
Keypair { sk, pk }
})
.collect();
let validators = keypairs
.iter()
.map(|keypair| {
let withdrawal_credentials = Hash256::from_slice(&get_withdrawal_credentials(
&keypair.pk,
spec.bls_withdrawal_prefix_byte,
));
Validator {
pubkey: keypair.pk.clone(),
withdrawal_credentials,
activation_epoch: spec.far_future_epoch,
exit_epoch: spec.far_future_epoch,
withdrawable_epoch: spec.far_future_epoch,
initiated_exit: false,
slashed: false,
}
})
.collect();
let mut state_builder = BeaconStateBuilder::new(
0,
Eth1Data {
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
},
spec,
);
let balances = vec![32_000_000_000; validator_count];
state_builder.import_existing_validators(
validators,
balances,
validator_count as u64,
spec,
);
Self {
state: state_builder.build(spec).unwrap(),
keypairs,
}
}
pub fn build(self) -> (BeaconState, Vec<Keypair>) {
(self.state, self.keypairs)
}
pub fn build_caches(&mut self, spec: &ChainSpec) -> Result<(), BeaconStateError> {
let state = &mut self.state;
state.build_epoch_cache(RelativeEpoch::Previous, &spec)?;
state.build_epoch_cache(RelativeEpoch::Current, &spec)?;
state.build_epoch_cache(RelativeEpoch::Next, &spec)?;
Ok(())
}
/// Sets the `BeaconState` to be in a slot, calling `teleport_to_epoch` to update the epoch.
pub fn teleport_to_slot(&mut self, slot: Slot, spec: &ChainSpec) {
self.teleport_to_epoch(slot.epoch(spec.slots_per_epoch), spec);
self.state.slot = slot;
}
/// Sets the `BeaconState` to be in the first 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).
fn teleport_to_epoch(&mut self, epoch: Epoch, spec: &ChainSpec) {
let state = &mut self.state;
let slot = epoch.start_slot(spec.slots_per_epoch);
state.slot = slot;
state.previous_shuffling_epoch = epoch - 1;
state.current_shuffling_epoch = epoch;
state.previous_shuffling_seed = Hash256::from_low_u64_le(0);
state.current_shuffling_seed = Hash256::from_low_u64_le(1);
state.previous_justified_epoch = epoch - 3;
state.justified_epoch = epoch - 2;
state.justification_bitfield = u64::max_value();
state.finalized_epoch = epoch - 3;
state.validator_registry_update_epoch = epoch - 3;
}
/// 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, spec: &ChainSpec) {
let state = &mut self.state;
state
.build_epoch_cache(RelativeEpoch::Previous, spec)
.unwrap();
state
.build_epoch_cache(RelativeEpoch::Current, spec)
.unwrap();
let current_epoch = state.current_epoch(spec);
let previous_epoch = state.previous_epoch(spec);
let first_slot = previous_epoch.start_slot(spec.slots_per_epoch).as_u64();
let last_slot = current_epoch.end_slot(spec.slots_per_epoch).as_u64()
- spec.min_attestation_inclusion_delay;
let last_slot = std::cmp::min(state.slot.as_u64(), last_slot);
for slot in first_slot..last_slot + 1 {
let slot = Slot::from(slot);
let committees = state
.get_crosslink_committees_at_slot(slot, spec)
.unwrap()
.clone();
for (committee, shard) in committees {
state
.latest_attestations
.push(committee_to_pending_attestation(
state, &committee, shard, slot, spec,
))
}
}
}
}
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.slots_per_epoch) != slot.epoch(spec.slots_per_epoch);
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.slots_per_epoch), spec)
.unwrap()
} else {
*state
.get_block_root(current_epoch.start_slot(spec.slots_per_epoch), spec)
.unwrap()
};
let justified_block_root = *state
.get_block_root(justified_epoch.start_slot(spec.slots_per_epoch), spec)
.unwrap();
PendingAttestation {
aggregation_bitfield,
data: AttestationData {
slot,
shard,
beacon_block_root: *state.get_block_root(slot, spec).unwrap(),
epoch_boundary_root,
crosslink_data_root: Hash256::zero(),
latest_crosslink: Crosslink {
epoch: slot.epoch(spec.slots_per_epoch),
crosslink_data_root: Hash256::zero(),
},
justified_epoch,
justified_block_root,
},
custody_bitfield,
inclusion_slot: slot + spec.min_attestation_inclusion_delay,
}
}