From f8ec1e0cfa381610c8f19adda6a0a47bca3da427 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 9 Mar 2019 18:56:01 +1100 Subject: [PATCH] Add slashings and attestations to per block benching --- .../benches/block_processing_benches.rs | 84 +++++- .../src/beacon_block_bencher.rs | 241 +++++++++++++++++- eth2/types/src/attester_slashing/builder.rs | 1 + 3 files changed, 322 insertions(+), 4 deletions(-) diff --git a/eth2/state_processing/benches/block_processing_benches.rs b/eth2/state_processing/benches/block_processing_benches.rs index b5fdaa5bd..75943b1ad 100644 --- a/eth2/state_processing/benches/block_processing_benches.rs +++ b/eth2/state_processing/benches/block_processing_benches.rs @@ -20,6 +20,39 @@ pub fn block_processing_16k_validators(c: &mut Criterion) { let (state, keypairs) = build_state(validator_count, &spec); let block = build_block(&state, &keypairs, &spec); + assert_eq!( + block.body.proposer_slashings.len(), + spec.max_proposer_slashings as usize, + "The block should have the maximum possible proposer slashings" + ); + + assert_eq!( + block.body.attester_slashings.len(), + spec.max_attester_slashings as usize, + "The block should have the maximum possible attester slashings" + ); + + for attester_slashing in &block.body.attester_slashings { + let len_1 = attester_slashing + .slashable_attestation_1 + .validator_indices + .len(); + let len_2 = attester_slashing + .slashable_attestation_1 + .validator_indices + .len(); + assert!( + (len_1 == len_2) && (len_2 == spec.max_indices_per_slashable_vote as usize), + "Each attester slashing should have the maximum possible validator indices" + ); + } + + assert_eq!( + block.body.attestations.len(), + spec.max_attestations as usize, + "The block should have the maximum possible attestations." + ); + bench_block_processing( c, &block, @@ -52,6 +85,45 @@ fn build_block(state: &BeaconState, keypairs: &[Keypair], spec: &ChainSpec) -> B builder.set_randao_reveal(&keypair.sk, &state.fork, spec); + // Insert the maximum possible number of `ProposerSlashing` objects. + for validator_index in 0..spec.max_proposer_slashings { + builder.insert_proposer_slashing( + validator_index, + &keypairs[validator_index as usize].sk, + &state.fork, + spec, + ); + } + + // Insert the maximum possible number of `AttesterSlashing` objects + let number_of_slashable_attesters = + spec.max_indices_per_slashable_vote * spec.max_attester_slashings; + let all_attester_slashing_indices: Vec = (spec.max_proposer_slashings + ..(spec.max_proposer_slashings + number_of_slashable_attesters)) + .collect(); + let attester_slashing_groups: Vec<&[u64]> = all_attester_slashing_indices + .chunks(spec.max_indices_per_slashable_vote as usize) + .collect(); + for attester_slashing_group in attester_slashing_groups { + let attester_slashing_keypairs: Vec<&SecretKey> = attester_slashing_group + .iter() + .map(|&validator_index| &keypairs[validator_index as usize].sk) + .collect(); + + builder.insert_attester_slashing( + &attester_slashing_group, + &attester_slashing_keypairs, + &state.fork, + spec, + ); + } + + // Insert the maximum possible number of `Attestation` objects. + let all_secret_keys: Vec<&SecretKey> = keypairs.iter().map(|keypair| &keypair.sk).collect(); + builder + .fill_with_attestations(state, &all_secret_keys, spec) + .unwrap(); + builder.build(&keypair.sk, &state.fork, spec) } @@ -153,7 +225,10 @@ fn bench_block_processing( ); let state = initial_state.clone(); - let block = initial_block.clone(); + let mut block = initial_block.clone(); + // Slashings will invalidate the attestations. + block.body.proposer_slashings = vec![]; + block.body.attester_slashings = vec![]; let spec = initial_spec.clone(); c.bench( &format!("block_processing_{}", desc), @@ -221,11 +296,14 @@ fn bench_block_processing( ); let state = initial_state.clone(); - let block = initial_block.clone(); + let mut block = initial_block.clone(); + // Slashings will invalidate the attestations. + block.body.proposer_slashings = vec![]; + block.body.attester_slashings = vec![]; let spec = initial_spec.clone(); c.bench( &format!("block_processing_{}", desc), - Benchmark::new("per_block_processing", move |b| { + Benchmark::new("per_block_processing_no_slashings", move |b| { b.iter_with_setup( || state.clone(), |mut state| black_box(per_block_processing(&mut state, &block, &spec).unwrap()), diff --git a/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs b/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs index 67b7ccc9d..989dbd929 100644 --- a/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs +++ b/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs @@ -1,5 +1,8 @@ +use rayon::prelude::*; use ssz::{SignedRoot, TreeHash}; -use types::*; +use types::{ + attester_slashing::AttesterSlashingBuilder, proposer_slashing::ProposerSlashingBuilder, *, +}; pub struct BeaconBlockBencher { block: BeaconBlock, @@ -33,6 +36,114 @@ impl BeaconBlockBencher { 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, Vec, 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 attestations_added == spec.max_attestations { + break; + } + if state.slot >= slot + spec.slots_per_epoch { + break; + } + + for (committee, shard) in state.get_crosslink_committees_at_slot(slot, spec)? { + 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 = committees + .par_iter() + .map(|(slot, committee, signing_validators, shard)| { + committee_to_attestation( + state, + &committee, + signing_validators, + secret_keys, + *shard, + *slot, + &state.fork, + spec, + ) + }) + .collect(); + + self.block.body.attestations.append(&mut attestations); + + Ok(()) + } + /// 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); @@ -44,3 +155,131 @@ impl BeaconBlockBencher { 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) +} + +/// Convert some committee into a valid `Attestation`. +/// +/// Note: `committee` must be the full committee for the attestation. `signing_validators` is a +/// list of validator indices that should sign the attestation. +fn committee_to_attestation( + state: &BeaconState, + committee: &[usize], + signing_validators: &[usize], + secret_keys: &[&SecretKey], + shard: u64, + slot: Slot, + fork: &Fork, + spec: &ChainSpec, +) -> Attestation { + let current_epoch = state.current_epoch(spec); + let previous_epoch = state.previous_epoch(spec); + + 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(); + + let data = AttestationData { + slot, + shard, + beacon_block_root: *state.get_block_root(slot, spec).unwrap(), + epoch_boundary_root, + crosslink_data_root: Hash256::zero(), + latest_crosslink: state.latest_crosslinks[shard as usize].clone(), + justified_epoch, + justified_block_root, + }; + + let mut aggregate_signature = AggregateSignature::new(); + let mut aggregation_bitfield = Bitfield::new(); + let mut custody_bitfield = Bitfield::new(); + + let message = AttestationDataAndCustodyBit { + data: data.clone(), + custody_bit: false, + } + .hash_tree_root(); + + let domain = spec.get_domain( + data.slot.epoch(spec.slots_per_epoch), + Domain::Attestation, + fork, + ); + + for (i, validator_index) in committee.iter().enumerate() { + custody_bitfield.set(i, false); + + if signing_validators + .iter() + .any(|&signer| *validator_index == signer) + { + aggregation_bitfield.set(i, true); + let signature = Signature::new(&message, domain, secret_keys[*validator_index]); + aggregate_signature.add(&signature); + } else { + aggregation_bitfield.set(i, false); + } + } + + Attestation { + aggregation_bitfield, + data, + custody_bitfield, + aggregate_signature, + } +} diff --git a/eth2/types/src/attester_slashing/builder.rs b/eth2/types/src/attester_slashing/builder.rs index 05301f30b..8edf4ed65 100644 --- a/eth2/types/src/attester_slashing/builder.rs +++ b/eth2/types/src/attester_slashing/builder.rs @@ -66,6 +66,7 @@ impl AttesterSlashingBuilder { let add_signatures = |attestation: &mut SlashableAttestation| { for (i, validator_index) in validator_indices.iter().enumerate() { + attestation.custody_bitfield.set(i, false); let attestation_data_and_custody_bit = AttestationDataAndCustodyBit { data: attestation.data.clone(), custody_bit: attestation.custody_bitfield.get(i).unwrap(),