mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-16 20:39:10 +00:00
Merge pull request #264 from sigp/yaml-chain-tests
YAML-defined test_harness routines
This commit is contained in:
@@ -1,19 +1,14 @@
|
||||
use self::verify_slashable_attestation::verify_slashable_attestation;
|
||||
use crate::SlotProcessingError;
|
||||
use hashing::hash;
|
||||
use int_to_bytes::int_to_bytes32;
|
||||
use log::{debug, trace};
|
||||
use ssz::{ssz_encode, TreeHash};
|
||||
use types::{
|
||||
AggregatePublicKey, Attestation, BeaconBlock, BeaconState, BeaconStateError, ChainSpec,
|
||||
Crosslink, Epoch, Exit, Fork, Hash256, PendingAttestation, PublicKey, RelativeEpoch, Signature,
|
||||
};
|
||||
use types::*;
|
||||
|
||||
mod verify_slashable_attestation;
|
||||
|
||||
// TODO: define elsehwere.
|
||||
const DOMAIN_PROPOSAL: u64 = 2;
|
||||
const DOMAIN_EXIT: u64 = 3;
|
||||
const DOMAIN_RANDAO: u64 = 4;
|
||||
const PHASE_0_CUSTODY_BIT: bool = false;
|
||||
const DOMAIN_ATTESTATION: u64 = 1;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
@@ -31,10 +26,13 @@ pub enum Error {
|
||||
BadRandaoSignature,
|
||||
MaxProposerSlashingsExceeded,
|
||||
BadProposerSlashing,
|
||||
MaxAttesterSlashingsExceed,
|
||||
MaxAttestationsExceeded,
|
||||
BadAttesterSlashing,
|
||||
InvalidAttestation(AttestationValidationError),
|
||||
NoBlockRoot,
|
||||
MaxDepositsExceeded,
|
||||
BadDeposit,
|
||||
MaxExitsExceeded,
|
||||
BadExit,
|
||||
BadCustodyReseeds,
|
||||
@@ -89,7 +87,7 @@ impl BlockProcessable for BeaconState {
|
||||
}
|
||||
|
||||
fn per_block_processing_signature_optional(
|
||||
state: &mut BeaconState,
|
||||
mut state: &mut BeaconState,
|
||||
block: &BeaconBlock,
|
||||
verify_block_signature: bool,
|
||||
spec: &ChainSpec,
|
||||
@@ -113,7 +111,7 @@ fn per_block_processing_signature_optional(
|
||||
&block_proposer.pubkey,
|
||||
&block.proposal_root(spec)[..],
|
||||
&block.signature,
|
||||
get_domain(&state.fork, state.current_epoch(spec), DOMAIN_PROPOSAL)
|
||||
get_domain(&state.fork, state.current_epoch(spec), spec.domain_proposal)
|
||||
),
|
||||
Error::BadBlockSignature
|
||||
);
|
||||
@@ -127,7 +125,7 @@ fn per_block_processing_signature_optional(
|
||||
&block_proposer.pubkey,
|
||||
&int_to_bytes32(state.current_epoch(spec).as_u64()),
|
||||
&block.randao_reveal,
|
||||
get_domain(&state.fork, state.current_epoch(spec), DOMAIN_RANDAO)
|
||||
get_domain(&state.fork, state.current_epoch(spec), spec.domain_randao)
|
||||
),
|
||||
Error::BadRandaoSignature
|
||||
);
|
||||
@@ -188,7 +186,7 @@ fn per_block_processing_signature_optional(
|
||||
.proposal_data_1
|
||||
.slot
|
||||
.epoch(spec.epoch_length),
|
||||
DOMAIN_PROPOSAL
|
||||
spec.domain_proposal
|
||||
)
|
||||
),
|
||||
Error::BadProposerSlashing
|
||||
@@ -204,7 +202,7 @@ fn per_block_processing_signature_optional(
|
||||
.proposal_data_2
|
||||
.slot
|
||||
.epoch(spec.epoch_length),
|
||||
DOMAIN_PROPOSAL
|
||||
spec.domain_proposal
|
||||
)
|
||||
),
|
||||
Error::BadProposerSlashing
|
||||
@@ -212,6 +210,17 @@ fn per_block_processing_signature_optional(
|
||||
state.penalize_validator(proposer_slashing.proposer_index as usize, spec)?;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attester slashings
|
||||
*/
|
||||
ensure!(
|
||||
block.body.attester_slashings.len() as u64 <= spec.max_attester_slashings,
|
||||
Error::MaxAttesterSlashingsExceed
|
||||
);
|
||||
for attester_slashing in &block.body.attester_slashings {
|
||||
verify_slashable_attestation(&mut state, &attester_slashing, spec)?;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attestations
|
||||
*/
|
||||
@@ -242,7 +251,27 @@ fn per_block_processing_signature_optional(
|
||||
Error::MaxDepositsExceeded
|
||||
);
|
||||
|
||||
// TODO: process deposits.
|
||||
// TODO: verify deposit merkle branches.
|
||||
for deposit in &block.body.deposits {
|
||||
debug!(
|
||||
"Processing deposit for pubkey {:?}",
|
||||
deposit.deposit_data.deposit_input.pubkey
|
||||
);
|
||||
state
|
||||
.process_deposit(
|
||||
deposit.deposit_data.deposit_input.pubkey.clone(),
|
||||
deposit.deposit_data.amount,
|
||||
deposit
|
||||
.deposit_data
|
||||
.deposit_input
|
||||
.proof_of_possession
|
||||
.clone(),
|
||||
deposit.deposit_data.deposit_input.withdrawal_credentials,
|
||||
None,
|
||||
spec,
|
||||
)
|
||||
.map_err(|_| Error::BadDeposit)?;
|
||||
}
|
||||
|
||||
/*
|
||||
* Exits
|
||||
@@ -276,7 +305,7 @@ fn per_block_processing_signature_optional(
|
||||
&validator.pubkey,
|
||||
&exit_message,
|
||||
&exit.signature,
|
||||
get_domain(&state.fork, exit.epoch, DOMAIN_EXIT)
|
||||
get_domain(&state.fork, exit.epoch, spec.domain_exit)
|
||||
),
|
||||
Error::BadProposerSlashing
|
||||
);
|
||||
@@ -370,11 +399,7 @@ fn validate_attestation_signature_optional(
|
||||
);
|
||||
let mut group_public_key = AggregatePublicKey::new();
|
||||
for participant in participants {
|
||||
group_public_key.add(
|
||||
state.validator_registry[participant as usize]
|
||||
.pubkey
|
||||
.as_raw(),
|
||||
)
|
||||
group_public_key.add(&state.validator_registry[participant as usize].pubkey)
|
||||
}
|
||||
ensure!(
|
||||
attestation.verify_signature(
|
||||
@@ -383,7 +408,7 @@ fn validate_attestation_signature_optional(
|
||||
get_domain(
|
||||
&state.fork,
|
||||
attestation.data.slot.epoch(spec.epoch_length),
|
||||
DOMAIN_ATTESTATION,
|
||||
spec.domain_attestation,
|
||||
)
|
||||
),
|
||||
AttestationValidationError::BadSignature
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
use super::Error;
|
||||
use types::*;
|
||||
|
||||
macro_rules! ensure {
|
||||
($condition: expr, $result: expr) => {
|
||||
if !$condition {
|
||||
return Err($result);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns `Ok(())` if some `AttesterSlashing` is valid to be included in some `BeaconState`,
|
||||
/// otherwise returns an `Err`.
|
||||
pub fn verify_slashable_attestation(
|
||||
state: &mut BeaconState,
|
||||
attester_slashing: &AttesterSlashing,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let slashable_attestation_1 = &attester_slashing.slashable_attestation_1;
|
||||
let slashable_attestation_2 = &attester_slashing.slashable_attestation_2;
|
||||
|
||||
ensure!(
|
||||
slashable_attestation_1.data != slashable_attestation_2.data,
|
||||
Error::BadAttesterSlashing
|
||||
);
|
||||
ensure!(
|
||||
slashable_attestation_1.is_double_vote(slashable_attestation_2, spec)
|
||||
| slashable_attestation_1.is_surround_vote(slashable_attestation_2, spec),
|
||||
Error::BadAttesterSlashing
|
||||
);
|
||||
ensure!(
|
||||
state.verify_slashable_attestation(&slashable_attestation_1, spec),
|
||||
Error::BadAttesterSlashing
|
||||
);
|
||||
ensure!(
|
||||
state.verify_slashable_attestation(&slashable_attestation_2, spec),
|
||||
Error::BadAttesterSlashing
|
||||
);
|
||||
|
||||
let mut slashable_indices = vec![];
|
||||
for i in &slashable_attestation_1.validator_indices {
|
||||
let validator = state
|
||||
.validator_registry
|
||||
.get(*i as usize)
|
||||
.ok_or_else(|| Error::BadAttesterSlashing)?;
|
||||
|
||||
if slashable_attestation_1.validator_indices.contains(&i)
|
||||
& !validator.is_penalized_at(state.current_epoch(spec))
|
||||
{
|
||||
slashable_indices.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
ensure!(!slashable_indices.is_empty(), Error::BadAttesterSlashing);
|
||||
|
||||
for i in slashable_indices {
|
||||
state.penalize_validator(*i as usize, spec)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
24
eth2/utils/bls/src/aggregate_public_key.rs
Normal file
24
eth2/utils/bls/src/aggregate_public_key.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use super::PublicKey;
|
||||
use bls_aggregates::AggregatePublicKey as RawAggregatePublicKey;
|
||||
|
||||
/// A single BLS signature.
|
||||
///
|
||||
/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ
|
||||
/// serialization).
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct AggregatePublicKey(RawAggregatePublicKey);
|
||||
|
||||
impl AggregatePublicKey {
|
||||
pub fn new() -> Self {
|
||||
AggregatePublicKey(RawAggregatePublicKey::new())
|
||||
}
|
||||
|
||||
pub fn add(&mut self, public_key: &PublicKey) {
|
||||
self.0.add(public_key.as_raw())
|
||||
}
|
||||
|
||||
/// Returns the underlying signature.
|
||||
pub fn as_raw(&self) -> &RawAggregatePublicKey {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
use super::{AggregatePublicKey, Signature};
|
||||
use bls_aggregates::AggregateSignature as RawAggregateSignature;
|
||||
use bls_aggregates::{
|
||||
AggregatePublicKey as RawAggregatePublicKey, AggregateSignature as RawAggregateSignature,
|
||||
};
|
||||
use serde::ser::{Serialize, Serializer};
|
||||
use ssz::{
|
||||
decode_ssz_list, hash, ssz_encode, Decodable, DecodeError, Encodable, SszStream, TreeHash,
|
||||
@@ -33,7 +35,38 @@ impl AggregateSignature {
|
||||
domain: u64,
|
||||
aggregate_public_key: &AggregatePublicKey,
|
||||
) -> bool {
|
||||
self.0.verify(msg, domain, aggregate_public_key)
|
||||
self.0.verify(msg, domain, aggregate_public_key.as_raw())
|
||||
}
|
||||
|
||||
/// Verify this AggregateSignature against multiple AggregatePublickeys with multiple Messages.
|
||||
///
|
||||
/// All PublicKeys related to a Message should be aggregated into one AggregatePublicKey.
|
||||
/// Each AggregatePublicKey has a 1:1 ratio with a 32 byte Message.
|
||||
pub fn verify_multiple(
|
||||
&self,
|
||||
messages: &[&[u8]],
|
||||
domain: u64,
|
||||
aggregate_public_keys: &[&AggregatePublicKey],
|
||||
) -> bool {
|
||||
// TODO: the API for `RawAggregatePublicKey` shoudn't need to take an owned
|
||||
// `AggregatePublicKey`. There is an issue to fix this, but in the meantime we need to
|
||||
// clone.
|
||||
//
|
||||
// https://github.com/sigp/signature-schemes/issues/10
|
||||
let aggregate_public_keys: Vec<RawAggregatePublicKey> = aggregate_public_keys
|
||||
.iter()
|
||||
.map(|pk| pk.as_raw())
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
// Messages are concatenated into one long message.
|
||||
let mut msg: Vec<u8> = vec![];
|
||||
for message in messages {
|
||||
msg.extend_from_slice(message);
|
||||
}
|
||||
|
||||
self.0
|
||||
.verify_multiple(&msg[..], domain, &aggregate_public_keys[..])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
extern crate bls_aggregates;
|
||||
extern crate ssz;
|
||||
|
||||
mod aggregate_public_key;
|
||||
mod aggregate_signature;
|
||||
mod keypair;
|
||||
mod public_key;
|
||||
mod secret_key;
|
||||
mod signature;
|
||||
|
||||
pub use crate::aggregate_public_key::AggregatePublicKey;
|
||||
pub use crate::aggregate_signature::AggregateSignature;
|
||||
pub use crate::keypair::Keypair;
|
||||
pub use crate::public_key::PublicKey;
|
||||
pub use crate::secret_key::SecretKey;
|
||||
pub use crate::signature::Signature;
|
||||
|
||||
pub use self::bls_aggregates::AggregatePublicKey;
|
||||
|
||||
pub const BLS_AGG_SIG_BYTE_SIZE: usize = 96;
|
||||
|
||||
use ssz::ssz_encode;
|
||||
|
||||
Reference in New Issue
Block a user