Merge pull request #264 from sigp/yaml-chain-tests

YAML-defined test_harness routines
This commit is contained in:
Age Manning
2019-03-04 13:19:25 +11:00
committed by GitHub
26 changed files with 1543 additions and 75 deletions

View File

@@ -4,6 +4,10 @@ use serde_derive::Serialize;
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
mod builder;
pub use builder::AttesterSlashingBuilder;
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)]
pub struct AttesterSlashing {
pub slashable_attestation_1: SlashableAttestation,

View File

@@ -0,0 +1,96 @@
use crate::*;
use ssz::TreeHash;
/// Builds an `AttesterSlashing`.
pub struct AttesterSlashingBuilder();
impl AttesterSlashingBuilder {
/// Builds an `AttesterSlashing` that is a double vote.
///
/// The `signer` function is used to sign the double-vote and accepts:
///
/// - `validator_index: u64`
/// - `message: &[u8]`
/// - `epoch: Epoch`
/// - `domain: u64`
///
/// Where domain is a domain "constant" (e.g., `spec.domain_attestation`).
pub fn double_vote<F>(
validator_indices: &[u64],
signer: F,
spec: &ChainSpec,
) -> AttesterSlashing
where
F: Fn(u64, &[u8], Epoch, u64) -> Signature,
{
let double_voted_slot = Slot::new(0);
let shard = 0;
let justified_epoch = Epoch::new(0);
let epoch = Epoch::new(0);
let hash_1 = Hash256::from(&[1][..]);
let hash_2 = Hash256::from(&[2][..]);
let mut slashable_attestation_1 = SlashableAttestation {
validator_indices: validator_indices.to_vec(),
data: AttestationData {
slot: double_voted_slot,
shard,
beacon_block_root: hash_1,
epoch_boundary_root: hash_1,
shard_block_root: hash_1,
latest_crosslink: Crosslink {
epoch,
shard_block_root: hash_1,
},
justified_epoch,
justified_block_root: hash_1,
},
custody_bitfield: Bitfield::new(),
aggregate_signature: AggregateSignature::new(),
};
let mut slashable_attestation_2 = SlashableAttestation {
validator_indices: validator_indices.to_vec(),
data: AttestationData {
slot: double_voted_slot,
shard,
beacon_block_root: hash_2,
epoch_boundary_root: hash_2,
shard_block_root: hash_2,
latest_crosslink: Crosslink {
epoch,
shard_block_root: hash_2,
},
justified_epoch,
justified_block_root: hash_2,
},
custody_bitfield: Bitfield::new(),
aggregate_signature: AggregateSignature::new(),
};
let add_signatures = |attestation: &mut SlashableAttestation| {
for (i, validator_index) in validator_indices.iter().enumerate() {
let attestation_data_and_custody_bit = AttestationDataAndCustodyBit {
data: attestation.data.clone(),
custody_bit: attestation.custody_bitfield.get(i).unwrap(),
};
let message = attestation_data_and_custody_bit.hash_tree_root();
let signature = signer(
*validator_index,
&message[..],
epoch,
spec.domain_attestation,
);
attestation.aggregate_signature.add(&signature);
}
};
add_signatures(&mut slashable_attestation_1);
add_signatures(&mut slashable_attestation_2);
AttesterSlashing {
slashable_attestation_1,
slashable_attestation_2,
}
}
}

View File

@@ -1,14 +1,11 @@
use self::epoch_cache::EpochCache;
use crate::test_utils::TestRandom;
use crate::{
validator::StatusFlags, validator_registry::get_active_validator_indices, AttestationData,
Bitfield, ChainSpec, Crosslink, Deposit, DepositData, DepositInput, Epoch, Eth1Data,
Eth1DataVote, Fork, Hash256, PendingAttestation, PublicKey, Signature, Slot, Validator,
};
use crate::{validator::StatusFlags, validator_registry::get_active_validator_indices, *};
use bls::verify_proof_of_possession;
use honey_badger_split::SplitExt;
use log::{debug, error, trace};
use rand::RngCore;
use rayon::prelude::*;
use serde_derive::Serialize;
use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
use std::collections::HashMap;
@@ -199,10 +196,10 @@ impl BeaconState {
let mut genesis_state =
BeaconState::genesis_without_validators(genesis_time, latest_eth1_data, spec)?;
trace!("Processing genesis deposits...");
debug!("Processing genesis deposits...");
let deposit_data = initial_validator_deposits
.iter()
.par_iter()
.map(|deposit| &deposit.deposit_data)
.collect();
@@ -411,6 +408,8 @@ impl BeaconState {
return Err(Error::InsufficientValidators);
}
debug!("Shuffling {} validators...", active_validator_indices.len());
let committees_per_epoch =
self.get_epoch_committee_count(active_validator_indices.len(), spec);
@@ -420,8 +419,7 @@ impl BeaconState {
committees_per_epoch
);
let active_validator_indices: Vec<usize> =
active_validator_indices.iter().cloned().collect();
let active_validator_indices: Vec<usize> = active_validator_indices.to_vec();
let shuffled_active_validator_indices = shuffle_list(
active_validator_indices,
@@ -1000,6 +998,10 @@ impl BeaconState {
whistleblower_reward
);
self.validator_registry[validator_index].penalized_epoch = current_epoch;
debug!(
"Whistleblower {} penalized validator {}.",
whistleblower_index, validator_index
);
Ok(())
}
@@ -1138,6 +1140,114 @@ impl BeaconState {
)
}
/// Verify ``bitfield`` against the ``committee_size``.
///
/// Spec v0.2.0
pub fn verify_bitfield(&self, bitfield: &Bitfield, committee_size: usize) -> bool {
if bitfield.num_bytes() != ((committee_size + 7) / 8) {
return false;
}
for i in committee_size..(bitfield.num_bytes() * 8) {
match bitfield.get(i) {
Ok(bit) => {
if bit {
return false;
}
}
Err(_) => unreachable!(),
}
}
true
}
/// Verify validity of ``slashable_attestation`` fields.
///
/// Spec v0.2.0
pub fn verify_slashable_attestation(
&self,
slashable_attestation: &SlashableAttestation,
spec: &ChainSpec,
) -> bool {
if slashable_attestation.custody_bitfield.num_set_bits() > 0 {
return false;
}
if slashable_attestation.validator_indices.is_empty() {
return false;
}
for i in 0..(slashable_attestation.validator_indices.len() - 1) {
if slashable_attestation.validator_indices[i]
>= slashable_attestation.validator_indices[i + 1]
{
return false;
}
}
if !self.verify_bitfield(
&slashable_attestation.custody_bitfield,
slashable_attestation.validator_indices.len(),
) {
return false;
}
if slashable_attestation.validator_indices.len()
> spec.max_indices_per_slashable_vote as usize
{
return false;
}
let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2];
let mut message_exists = vec![false; 2];
for (i, v) in slashable_attestation.validator_indices.iter().enumerate() {
let custody_bit = match slashable_attestation.custody_bitfield.get(i) {
Ok(bit) => bit,
Err(_) => unreachable!(),
};
message_exists[custody_bit as usize] = true;
match self.validator_registry.get(*v as usize) {
Some(validator) => {
aggregate_pubs[custody_bit as usize].add(&validator.pubkey);
}
None => return false,
};
}
let message_0 = AttestationDataAndCustodyBit {
data: slashable_attestation.data.clone(),
custody_bit: false,
}
.hash_tree_root();
let message_1 = AttestationDataAndCustodyBit {
data: slashable_attestation.data.clone(),
custody_bit: true,
}
.hash_tree_root();
let mut messages = vec![];
let mut keys = vec![];
if message_exists[0] {
messages.push(&message_0[..]);
keys.push(&aggregate_pubs[0]);
}
if message_exists[1] {
messages.push(&message_1[..]);
keys.push(&aggregate_pubs[1]);
}
slashable_attestation.aggregate_signature.verify_multiple(
&messages[..],
spec.domain_attestation,
&keys[..],
)
}
/// Return the block root at a recent `slot`.
///
/// Spec v0.2.0

View File

@@ -6,6 +6,10 @@ use serde_derive::Serialize;
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
mod builder;
pub use builder::ProposerSlashingBuilder;
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)]
pub struct ProposerSlashing {
pub proposer_index: u64,

View File

@@ -0,0 +1,59 @@
use crate::*;
use ssz::TreeHash;
/// Builds a `ProposerSlashing`.
pub struct ProposerSlashingBuilder();
impl ProposerSlashingBuilder {
/// Builds a `ProposerSlashing` that is a double vote.
///
/// The `signer` function is used to sign the double-vote and accepts:
///
/// - `validator_index: u64`
/// - `message: &[u8]`
/// - `epoch: Epoch`
/// - `domain: u64`
///
/// Where domain is a domain "constant" (e.g., `spec.domain_attestation`).
pub fn double_vote<F>(proposer_index: u64, signer: F, spec: &ChainSpec) -> ProposerSlashing
where
F: Fn(u64, &[u8], Epoch, u64) -> Signature,
{
let slot = Slot::new(0);
let shard = 0;
let proposal_data_1 = ProposalSignedData {
slot,
shard,
block_root: Hash256::from(&[1][..]),
};
let proposal_data_2 = ProposalSignedData {
slot,
shard,
block_root: Hash256::from(&[2][..]),
};
let proposal_signature_1 = {
let message = proposal_data_1.hash_tree_root();
let epoch = slot.epoch(spec.epoch_length);
let domain = spec.domain_proposal;
signer(proposer_index, &message[..], epoch, domain)
};
let proposal_signature_2 = {
let message = proposal_data_2.hash_tree_root();
let epoch = slot.epoch(spec.epoch_length);
let domain = spec.domain_proposal;
signer(proposer_index, &message[..], epoch, domain)
};
ProposerSlashing {
proposer_index,
proposal_data_1,
proposal_signature_1,
proposal_data_2,
proposal_signature_2,
}
}
}

View File

@@ -1,4 +1,4 @@
use crate::{test_utils::TestRandom, AggregateSignature, AttestationData, Bitfield};
use crate::{test_utils::TestRandom, AggregateSignature, AttestationData, Bitfield, ChainSpec};
use rand::RngCore;
use serde_derive::Serialize;
use ssz_derive::{Decode, Encode, TreeHash};
@@ -12,12 +12,107 @@ pub struct SlashableAttestation {
pub aggregate_signature: AggregateSignature,
}
impl SlashableAttestation {
/// Check if ``attestation_data_1`` and ``attestation_data_2`` have the same target.
///
/// Spec v0.3.0
pub fn is_double_vote(&self, other: &SlashableAttestation, spec: &ChainSpec) -> bool {
self.data.slot.epoch(spec.epoch_length) == other.data.slot.epoch(spec.epoch_length)
}
/// Check if ``attestation_data_1`` surrounds ``attestation_data_2``.
///
/// Spec v0.3.0
pub fn is_surround_vote(&self, other: &SlashableAttestation, spec: &ChainSpec) -> bool {
let source_epoch_1 = self.data.justified_epoch;
let source_epoch_2 = other.data.justified_epoch;
let target_epoch_1 = self.data.slot.epoch(spec.epoch_length);
let target_epoch_2 = other.data.slot.epoch(spec.epoch_length);
(source_epoch_1 < source_epoch_2) && (target_epoch_2 < target_epoch_1)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chain_spec::ChainSpec;
use crate::slot_epoch::{Epoch, Slot};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[test]
pub fn test_is_double_vote_true() {
let spec = ChainSpec::foundation();
let slashable_vote_first = create_slashable_attestation(1, 1, &spec);
let slashable_vote_second = create_slashable_attestation(1, 1, &spec);
assert_eq!(
slashable_vote_first.is_double_vote(&slashable_vote_second, &spec),
true
)
}
#[test]
pub fn test_is_double_vote_false() {
let spec = ChainSpec::foundation();
let slashable_vote_first = create_slashable_attestation(1, 1, &spec);
let slashable_vote_second = create_slashable_attestation(2, 1, &spec);
assert_eq!(
slashable_vote_first.is_double_vote(&slashable_vote_second, &spec),
false
);
}
#[test]
pub fn test_is_surround_vote_true() {
let spec = ChainSpec::foundation();
let slashable_vote_first = create_slashable_attestation(2, 1, &spec);
let slashable_vote_second = create_slashable_attestation(1, 2, &spec);
assert_eq!(
slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec),
true
);
}
#[test]
pub fn test_is_surround_vote_true_realistic() {
let spec = ChainSpec::foundation();
let slashable_vote_first = create_slashable_attestation(4, 1, &spec);
let slashable_vote_second = create_slashable_attestation(3, 2, &spec);
assert_eq!(
slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec),
true
);
}
#[test]
pub fn test_is_surround_vote_false_source_epoch_fails() {
let spec = ChainSpec::foundation();
let slashable_vote_first = create_slashable_attestation(2, 2, &spec);
let slashable_vote_second = create_slashable_attestation(1, 1, &spec);
assert_eq!(
slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec),
false
);
}
#[test]
pub fn test_is_surround_vote_false_target_epoch_fails() {
let spec = ChainSpec::foundation();
let slashable_vote_first = create_slashable_attestation(1, 1, &spec);
let slashable_vote_second = create_slashable_attestation(2, 2, &spec);
assert_eq!(
slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec),
false
);
}
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
@@ -40,4 +135,17 @@ mod tests {
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
fn create_slashable_attestation(
slot_factor: u64,
justified_epoch: u64,
spec: &ChainSpec,
) -> SlashableAttestation {
let mut rng = XorShiftRng::from_seed([42; 16]);
let mut slashable_vote = SlashableAttestation::random_for_test(&mut rng);
slashable_vote.data.slot = Slot::new(slot_factor * spec.epoch_length);
slashable_vote.data.justified_epoch = Epoch::new(justified_epoch);
slashable_vote
}
}

View File

@@ -54,9 +54,19 @@ pub struct Validator {
}
impl Validator {
/// This predicate indicates if the validator represented by this record is considered "active" at `slot`.
pub fn is_active_at(&self, slot: Epoch) -> bool {
self.activation_epoch <= slot && slot < self.exit_epoch
/// Returns `true` if the validator is considered active at some epoch.
pub fn is_active_at(&self, epoch: Epoch) -> bool {
self.activation_epoch <= epoch && epoch < self.exit_epoch
}
/// Returns `true` if the validator is considered exited at some epoch.
pub fn is_exited_at(&self, epoch: Epoch) -> bool {
self.exit_epoch <= epoch
}
/// Returns `true` if the validator is considered penalized at some epoch.
pub fn is_penalized_at(&self, epoch: Epoch) -> bool {
self.penalized_epoch <= epoch
}
}