mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-20 13:24:44 +00:00
Merge pull request #264 from sigp/yaml-chain-tests
YAML-defined test_harness routines
This commit is contained in:
@@ -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,
|
||||
|
||||
96
eth2/types/src/attester_slashing/builder.rs
Normal file
96
eth2/types/src/attester_slashing/builder.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
59
eth2/types/src/proposer_slashing/builder.rs
Normal file
59
eth2/types/src/proposer_slashing/builder.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user