spec v0.6.1: attestation processing/verif
This commit is contained in:
parent
f8d4e742ad
commit
0b2aa26f2d
29
eth2/state_processing/src/common/convert_to_indexed.rs
Normal file
29
eth2/state_processing/src/common/convert_to_indexed.rs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
use super::get_attesting_indices;
|
||||||
|
use itertools::{Either, Itertools};
|
||||||
|
use types::*;
|
||||||
|
|
||||||
|
/// Convert `attestation` to (almost) indexed-verifiable form.
|
||||||
|
///
|
||||||
|
/// Spec v0.6.1
|
||||||
|
pub fn convert_to_indexed<T: EthSpec>(
|
||||||
|
state: &BeaconState<T>,
|
||||||
|
attestation: &Attestation,
|
||||||
|
) -> Result<IndexedAttestation, BeaconStateError> {
|
||||||
|
let attesting_indices =
|
||||||
|
get_attesting_indices(state, &attestation.data, &attestation.aggregation_bitfield)?;
|
||||||
|
|
||||||
|
let (custody_bit_0_indices, custody_bit_1_indices) =
|
||||||
|
attesting_indices.into_iter().enumerate().partition_map(
|
||||||
|
|(committee_idx, validator_idx)| match attestation.custody_bitfield.get(committee_idx) {
|
||||||
|
Ok(true) => Either::Right(validator_idx as u64),
|
||||||
|
_ => Either::Left(validator_idx as u64),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(IndexedAttestation {
|
||||||
|
custody_bit_0_indices,
|
||||||
|
custody_bit_1_indices,
|
||||||
|
data: attestation.data.clone(),
|
||||||
|
signature: attestation.signature.clone(),
|
||||||
|
})
|
||||||
|
}
|
@ -1,8 +1,10 @@
|
|||||||
|
mod convert_to_indexed;
|
||||||
mod exit;
|
mod exit;
|
||||||
mod get_attesting_indices;
|
mod get_attesting_indices;
|
||||||
mod slash_validator;
|
mod slash_validator;
|
||||||
mod verify_bitfield;
|
mod verify_bitfield;
|
||||||
|
|
||||||
|
pub use convert_to_indexed::convert_to_indexed;
|
||||||
pub use exit::initiate_validator_exit;
|
pub use exit::initiate_validator_exit;
|
||||||
pub use get_attesting_indices::{get_attesting_indices, get_attesting_indices_unsorted};
|
pub use get_attesting_indices::{get_attesting_indices, get_attesting_indices_unsorted};
|
||||||
pub use slash_validator::slash_validator;
|
pub use slash_validator::slash_validator;
|
||||||
|
@ -14,7 +14,9 @@ pub use validate_attestation::{
|
|||||||
};
|
};
|
||||||
pub use verify_deposit::{get_existing_validator_index, verify_deposit, verify_deposit_index};
|
pub use verify_deposit::{get_existing_validator_index, verify_deposit, verify_deposit_index};
|
||||||
pub use verify_exit::{verify_exit, verify_exit_time_independent_only};
|
pub use verify_exit::{verify_exit, verify_exit_time_independent_only};
|
||||||
pub use verify_indexed_attestation::verify_indexed_attestation;
|
pub use verify_indexed_attestation::{
|
||||||
|
verify_indexed_attestation, verify_indexed_attestation_without_signature,
|
||||||
|
};
|
||||||
pub use verify_transfer::{
|
pub use verify_transfer::{
|
||||||
execute_transfer, verify_transfer, verify_transfer_time_independent_only,
|
execute_transfer, verify_transfer, verify_transfer_time_independent_only,
|
||||||
};
|
};
|
||||||
@ -304,7 +306,7 @@ pub fn process_attester_slashings<T: EthSpec>(
|
|||||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||||
/// an `Err` describing the invalid object or cause of failure.
|
/// an `Err` describing the invalid object or cause of failure.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
pub fn process_attestations<T: EthSpec>(
|
pub fn process_attestations<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
attestations: &[Attestation],
|
attestations: &[Attestation],
|
||||||
@ -327,13 +329,20 @@ pub fn process_attestations<T: EthSpec>(
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Update the state in series.
|
// Update the state in series.
|
||||||
|
let proposer_index =
|
||||||
|
state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)? as u64;
|
||||||
for attestation in attestations {
|
for attestation in attestations {
|
||||||
let pending_attestation = PendingAttestation::from_attestation(attestation, state.slot);
|
let attestation_slot = state.get_attestation_slot(&attestation.data)?;
|
||||||
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
|
let pending_attestation = PendingAttestation {
|
||||||
|
aggregation_bitfield: attestation.aggregation_bitfield.clone(),
|
||||||
|
data: attestation.data.clone(),
|
||||||
|
inclusion_delay: (state.slot - attestation_slot).as_u64(),
|
||||||
|
proposer_index,
|
||||||
|
};
|
||||||
|
|
||||||
if attestation_epoch == state.current_epoch() {
|
if attestation.data.target_epoch == state.current_epoch() {
|
||||||
state.current_epoch_attestations.push(pending_attestation)
|
state.current_epoch_attestations.push(pending_attestation)
|
||||||
} else if attestation_epoch == state.previous_epoch(spec) {
|
} else {
|
||||||
state.previous_epoch_attestations.push(pending_attestation)
|
state.previous_epoch_attestations.push(pending_attestation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,6 +126,8 @@ pub enum AttestationInvalid {
|
|||||||
},
|
},
|
||||||
/// Attestation slot is too far in the past to be included in a block.
|
/// Attestation slot is too far in the past to be included in a block.
|
||||||
IncludedTooLate { state: Slot, attestation: Slot },
|
IncludedTooLate { state: Slot, attestation: Slot },
|
||||||
|
/// Attestation target epoch does not match the current or previous epoch.
|
||||||
|
BadTargetEpoch,
|
||||||
/// Attestation justified epoch does not match the states current or previous justified epoch.
|
/// Attestation justified epoch does not match the states current or previous justified epoch.
|
||||||
///
|
///
|
||||||
/// `is_current` is `true` if the attestation was compared to the
|
/// `is_current` is `true` if the attestation was compared to the
|
||||||
@ -170,11 +172,20 @@ pub enum AttestationInvalid {
|
|||||||
BadSignature,
|
BadSignature,
|
||||||
/// The shard block root was not set to zero. This is a phase 0 requirement.
|
/// The shard block root was not set to zero. This is a phase 0 requirement.
|
||||||
ShardBlockRootNotZero,
|
ShardBlockRootNotZero,
|
||||||
|
/// The indexed attestation created from this attestation was found to be invalid.
|
||||||
|
BadIndexedAttestation(IndexedAttestationInvalid),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_from_beacon_state_error!(AttestationValidationError);
|
impl_from_beacon_state_error!(AttestationValidationError);
|
||||||
impl_into_with_index_with_beacon_error!(AttestationValidationError, AttestationInvalid);
|
impl_into_with_index_with_beacon_error!(AttestationValidationError, AttestationInvalid);
|
||||||
|
|
||||||
|
impl From<IndexedAttestationValidationError> for AttestationValidationError {
|
||||||
|
fn from(err: IndexedAttestationValidationError) -> Self {
|
||||||
|
let IndexedAttestationValidationError::Invalid(e) = err;
|
||||||
|
AttestationValidationError::Invalid(AttestationInvalid::BadIndexedAttestation(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* `AttesterSlashing` Validation
|
* `AttesterSlashing` Validation
|
||||||
*/
|
*/
|
||||||
@ -224,25 +235,23 @@ pub enum IndexedAttestationValidationError {
|
|||||||
/// Describes why an object is invalid.
|
/// Describes why an object is invalid.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum IndexedAttestationInvalid {
|
pub enum IndexedAttestationInvalid {
|
||||||
|
/// The custody bit 0 validators intersect with the bit 1 validators.
|
||||||
|
CustodyBitValidatorsIntersect,
|
||||||
/// The custody bitfield has some bits set `true`. This is not allowed in phase 0.
|
/// The custody bitfield has some bits set `true`. This is not allowed in phase 0.
|
||||||
CustodyBitfieldHasSetBits,
|
CustodyBitfieldHasSetBits,
|
||||||
/// No validator indices were specified.
|
/// No validator indices were specified.
|
||||||
NoValidatorIndices,
|
NoValidatorIndices,
|
||||||
|
/// The number of indices exceeds the global maximum.
|
||||||
|
///
|
||||||
|
/// (max_indices, indices_given)
|
||||||
|
MaxIndicesExceed(u64, usize),
|
||||||
/// The validator indices were not in increasing order.
|
/// The validator indices were not in increasing order.
|
||||||
///
|
///
|
||||||
/// The error occured between the given `index` and `index + 1`
|
/// The error occured between the given `index` and `index + 1`
|
||||||
BadValidatorIndicesOrdering(usize),
|
BadValidatorIndicesOrdering(usize),
|
||||||
/// The custody bitfield length is not the smallest possible size to represent the validators.
|
|
||||||
///
|
|
||||||
/// (validators_len, bitfield_len)
|
|
||||||
BadCustodyBitfieldLength(usize, usize),
|
|
||||||
/// The number of slashable indices exceed the global maximum.
|
|
||||||
///
|
|
||||||
/// (max_indices, indices_given)
|
|
||||||
MaxIndicesExceed(usize, usize),
|
|
||||||
/// The validator index is unknown. One cannot slash one who does not exist.
|
/// The validator index is unknown. One cannot slash one who does not exist.
|
||||||
UnknownValidator(u64),
|
UnknownValidator(u64),
|
||||||
/// The slashable attestation aggregate signature was not valid.
|
/// The indexed attestation aggregate signature was not valid.
|
||||||
BadSignature,
|
BadSignature,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
use super::errors::{AttestationInvalid as Invalid, AttestationValidationError as Error};
|
use super::errors::{AttestationInvalid as Invalid, AttestationValidationError as Error};
|
||||||
use crate::common::verify_bitfield_length;
|
use crate::common::convert_to_indexed;
|
||||||
|
use crate::per_block_processing::{
|
||||||
|
verify_indexed_attestation, verify_indexed_attestation_without_signature,
|
||||||
|
};
|
||||||
use tree_hash::TreeHash;
|
use tree_hash::TreeHash;
|
||||||
use types::*;
|
use types::*;
|
||||||
|
|
||||||
@ -8,7 +11,7 @@ use types::*;
|
|||||||
///
|
///
|
||||||
/// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity.
|
/// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
pub fn validate_attestation<T: EthSpec>(
|
pub fn validate_attestation<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
attestation: &Attestation,
|
attestation: &Attestation,
|
||||||
@ -31,7 +34,7 @@ pub fn validate_attestation_time_independent_only<T: EthSpec>(
|
|||||||
///
|
///
|
||||||
/// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity.
|
/// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
pub fn validate_attestation_without_signature<T: EthSpec>(
|
pub fn validate_attestation_without_signature<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
attestation: &Attestation,
|
attestation: &Attestation,
|
||||||
@ -44,7 +47,7 @@ pub fn validate_attestation_without_signature<T: EthSpec>(
|
|||||||
/// given state, optionally validating the aggregate signature.
|
/// given state, optionally validating the aggregate signature.
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
fn validate_attestation_parametric<T: EthSpec>(
|
fn validate_attestation_parametric<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
attestation: &Attestation,
|
attestation: &Attestation,
|
||||||
@ -52,107 +55,29 @@ fn validate_attestation_parametric<T: EthSpec>(
|
|||||||
verify_signature: bool,
|
verify_signature: bool,
|
||||||
time_independent_only: bool,
|
time_independent_only: bool,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Can't submit pre-historic attestations.
|
let attestation_slot = state.get_attestation_slot(&attestation.data)?;
|
||||||
verify!(
|
|
||||||
attestation.data.slot >= spec.genesis_slot,
|
|
||||||
Invalid::PreGenesis {
|
|
||||||
genesis: spec.genesis_slot,
|
|
||||||
attestation: attestation.data.slot
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Can't submit attestations too far in history.
|
// Check attestation slot.
|
||||||
verify!(
|
|
||||||
state.slot <= attestation.data.slot + spec.slots_per_epoch,
|
|
||||||
Invalid::IncludedTooLate {
|
|
||||||
state: spec.genesis_slot,
|
|
||||||
attestation: attestation.data.slot
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Can't submit attestation too quickly.
|
|
||||||
verify!(
|
verify!(
|
||||||
time_independent_only
|
time_independent_only
|
||||||
|| attestation.data.slot + spec.min_attestation_inclusion_delay <= state.slot,
|
|| attestation_slot + spec.min_attestation_inclusion_delay <= state.slot,
|
||||||
Invalid::IncludedTooEarly {
|
Invalid::IncludedTooEarly {
|
||||||
state: state.slot,
|
state: state.slot,
|
||||||
delay: spec.min_attestation_inclusion_delay,
|
delay: spec.min_attestation_inclusion_delay,
|
||||||
attestation: attestation.data.slot
|
attestation: attestation_slot
|
||||||
|
}
|
||||||
|
);
|
||||||
|
verify!(
|
||||||
|
state.slot <= attestation_slot + spec.slots_per_epoch,
|
||||||
|
Invalid::IncludedTooLate {
|
||||||
|
state: state.slot,
|
||||||
|
attestation: attestation_slot
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify the justified epoch and root is correct.
|
// Verify the Casper FFG vote.
|
||||||
if !time_independent_only {
|
if !time_independent_only {
|
||||||
verify_justified_epoch_and_root(attestation, state, spec)?;
|
verify_casper_ffg_vote(attestation, state, spec)?;
|
||||||
}
|
|
||||||
|
|
||||||
// Check that the crosslink data is valid.
|
|
||||||
//
|
|
||||||
// Verify that either:
|
|
||||||
//
|
|
||||||
// (i)`state.latest_crosslinks[attestation.data.shard] == attestation.data.latest_crosslink`,
|
|
||||||
//
|
|
||||||
// (ii) `state.latest_crosslinks[attestation.data.shard] ==
|
|
||||||
// Crosslink(crosslink_data_root=attestation.data.crosslink_data_root,
|
|
||||||
// epoch=slot_to_epoch(attestation.data.slot))`.
|
|
||||||
let potential_crosslink = Crosslink {
|
|
||||||
crosslink_data_root: attestation.data.crosslink_data_root,
|
|
||||||
epoch: attestation.data.slot.epoch(spec.slots_per_epoch),
|
|
||||||
};
|
|
||||||
verify!(
|
|
||||||
(attestation.data.previous_crosslink
|
|
||||||
== state.latest_crosslinks[attestation.data.shard as usize])
|
|
||||||
| (state.latest_crosslinks[attestation.data.shard as usize] == potential_crosslink),
|
|
||||||
Invalid::BadPreviousCrosslink
|
|
||||||
);
|
|
||||||
|
|
||||||
// Attestation must be non-empty!
|
|
||||||
verify!(
|
|
||||||
attestation.aggregation_bitfield.num_set_bits() != 0,
|
|
||||||
Invalid::AggregationBitfieldIsEmpty
|
|
||||||
);
|
|
||||||
// Custody bitfield must be empty (be be removed in phase 1)
|
|
||||||
verify!(
|
|
||||||
attestation.custody_bitfield.num_set_bits() == 0,
|
|
||||||
Invalid::CustodyBitfieldHasSetBits
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get the committee for the specific shard that this attestation is for.
|
|
||||||
let crosslink_committee = state
|
|
||||||
.get_crosslink_committees_at_slot(attestation.data.slot, spec)?
|
|
||||||
.iter()
|
|
||||||
.find(|c| c.shard == attestation.data.shard)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
Error::Invalid(Invalid::NoCommitteeForShard {
|
|
||||||
shard: attestation.data.shard,
|
|
||||||
slot: attestation.data.slot,
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
let committee = &crosslink_committee.committee;
|
|
||||||
|
|
||||||
// Custody bitfield length is correct.
|
|
||||||
//
|
|
||||||
// This is not directly in the spec, but it is inferred.
|
|
||||||
verify!(
|
|
||||||
verify_bitfield_length(&attestation.custody_bitfield, committee.len()),
|
|
||||||
Invalid::BadCustodyBitfieldLength {
|
|
||||||
committee_len: committee.len(),
|
|
||||||
bitfield_len: attestation.custody_bitfield.len()
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// Aggregation bitfield length is correct.
|
|
||||||
//
|
|
||||||
// This is not directly in the spec, but it is inferred.
|
|
||||||
verify!(
|
|
||||||
verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()),
|
|
||||||
Invalid::BadAggregationBitfieldLength {
|
|
||||||
committee_len: committee.len(),
|
|
||||||
bitfield_len: attestation.custody_bitfield.len()
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if verify_signature {
|
|
||||||
verify_attestation_signature(state, committee, attestation, spec)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crosslink data root is zero (to be removed in phase 1).
|
// Crosslink data root is zero (to be removed in phase 1).
|
||||||
@ -161,145 +86,72 @@ fn validate_attestation_parametric<T: EthSpec>(
|
|||||||
Invalid::ShardBlockRootNotZero
|
Invalid::ShardBlockRootNotZero
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Check signature and bitfields
|
||||||
|
let indexed_attestation = convert_to_indexed(state, attestation)?;
|
||||||
|
if verify_signature {
|
||||||
|
verify_indexed_attestation(state, &indexed_attestation, spec)?;
|
||||||
|
} else {
|
||||||
|
verify_indexed_attestation_without_signature(state, &indexed_attestation, spec)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify that the `source_epoch` and `source_root` of an `Attestation` correctly
|
/// Check target epoch, source epoch, source root, and source crosslink.
|
||||||
/// match the current (or previous) justified epoch and root from the state.
|
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
fn verify_justified_epoch_and_root<T: EthSpec>(
|
fn verify_casper_ffg_vote<T: EthSpec>(
|
||||||
attestation: &Attestation,
|
attestation: &Attestation,
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let state_epoch = state.slot.epoch(spec.slots_per_epoch);
|
let data = &attestation.data;
|
||||||
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
|
if data.target_epoch == state.current_epoch() {
|
||||||
|
|
||||||
if attestation_epoch >= state_epoch {
|
|
||||||
verify!(
|
verify!(
|
||||||
attestation.data.source_epoch == state.current_justified_epoch,
|
data.source_epoch == state.current_justified_epoch,
|
||||||
Invalid::WrongJustifiedEpoch {
|
Invalid::WrongJustifiedEpoch {
|
||||||
state: state.current_justified_epoch,
|
state: state.current_justified_epoch,
|
||||||
attestation: attestation.data.source_epoch,
|
attestation: data.source_epoch,
|
||||||
is_current: true,
|
is_current: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
verify!(
|
verify!(
|
||||||
attestation.data.source_root == state.current_justified_root,
|
data.source_root == state.current_justified_root,
|
||||||
Invalid::WrongJustifiedRoot {
|
Invalid::WrongJustifiedRoot {
|
||||||
state: state.current_justified_root,
|
state: state.current_justified_root,
|
||||||
attestation: attestation.data.source_root,
|
attestation: data.source_root,
|
||||||
is_current: true,
|
is_current: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
verify!(
|
verify!(
|
||||||
attestation.data.source_epoch == state.previous_justified_epoch,
|
data.previous_crosslink_root
|
||||||
|
== Hash256::from_slice(&state.get_current_crosslink(data.shard)?.tree_hash_root()),
|
||||||
|
Invalid::BadPreviousCrosslink
|
||||||
|
);
|
||||||
|
} else if data.target_epoch == state.previous_epoch() {
|
||||||
|
verify!(
|
||||||
|
data.source_epoch == state.previous_justified_epoch,
|
||||||
Invalid::WrongJustifiedEpoch {
|
Invalid::WrongJustifiedEpoch {
|
||||||
state: state.previous_justified_epoch,
|
state: state.previous_justified_epoch,
|
||||||
attestation: attestation.data.source_epoch,
|
attestation: data.source_epoch,
|
||||||
is_current: false,
|
is_current: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
verify!(
|
verify!(
|
||||||
attestation.data.source_root == state.previous_justified_root,
|
data.source_root == state.previous_justified_root,
|
||||||
Invalid::WrongJustifiedRoot {
|
Invalid::WrongJustifiedRoot {
|
||||||
state: state.previous_justified_root,
|
state: state.previous_justified_root,
|
||||||
attestation: attestation.data.source_root,
|
attestation: data.source_root,
|
||||||
is_current: true,
|
is_current: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verifies an aggregate signature for some given `AttestationData`, returning `true` if the
|
|
||||||
/// `aggregate_signature` is valid.
|
|
||||||
///
|
|
||||||
/// Returns `false` if:
|
|
||||||
/// - `aggregate_signature` was not signed correctly.
|
|
||||||
/// - `custody_bitfield` does not have a bit for each index of `committee`.
|
|
||||||
/// - A `validator_index` in `committee` is not in `state.validator_registry`.
|
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
fn verify_attestation_signature<T: EthSpec>(
|
|
||||||
state: &BeaconState<T>,
|
|
||||||
committee: &[usize],
|
|
||||||
a: &Attestation,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2];
|
|
||||||
let mut message_exists = vec![false; 2];
|
|
||||||
let attestation_epoch = a.data.slot.epoch(spec.slots_per_epoch);
|
|
||||||
|
|
||||||
for (i, v) in committee.iter().enumerate() {
|
|
||||||
let validator_signed = a.aggregation_bitfield.get(i).map_err(|_| {
|
|
||||||
Error::Invalid(Invalid::BadAggregationBitfieldLength {
|
|
||||||
committee_len: committee.len(),
|
|
||||||
bitfield_len: a.aggregation_bitfield.len(),
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if validator_signed {
|
|
||||||
let custody_bit: bool = match a.custody_bitfield.get(i) {
|
|
||||||
Ok(bit) => bit,
|
|
||||||
// Invalidate signature if custody_bitfield.len() < committee
|
|
||||||
Err(_) => {
|
|
||||||
return Err(Error::Invalid(Invalid::BadCustodyBitfieldLength {
|
|
||||||
committee_len: committee.len(),
|
|
||||||
bitfield_len: a.aggregation_bitfield.len(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
message_exists[custody_bit as usize] = true;
|
|
||||||
|
|
||||||
match state.validator_registry.get(*v as usize) {
|
|
||||||
Some(validator) => {
|
|
||||||
aggregate_pubs[custody_bit as usize].add(&validator.pubkey);
|
|
||||||
}
|
|
||||||
// Return error if validator index is unknown.
|
|
||||||
None => return Err(Error::BeaconStateError(BeaconStateError::UnknownValidator)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message when custody bitfield is `false`
|
|
||||||
let message_0 = AttestationDataAndCustodyBit {
|
|
||||||
data: a.data.clone(),
|
|
||||||
custody_bit: false,
|
|
||||||
}
|
|
||||||
.tree_hash_root();
|
|
||||||
|
|
||||||
// Message when custody bitfield is `true`
|
|
||||||
let message_1 = AttestationDataAndCustodyBit {
|
|
||||||
data: a.data.clone(),
|
|
||||||
custody_bit: true,
|
|
||||||
}
|
|
||||||
.tree_hash_root();
|
|
||||||
|
|
||||||
let mut messages = vec![];
|
|
||||||
let mut keys = vec![];
|
|
||||||
|
|
||||||
// If any validator signed a message with a `false` custody bit.
|
|
||||||
if message_exists[0] {
|
|
||||||
messages.push(&message_0[..]);
|
|
||||||
keys.push(&aggregate_pubs[0]);
|
|
||||||
}
|
|
||||||
// If any validator signed a message with a `true` custody bit.
|
|
||||||
if message_exists[1] {
|
|
||||||
messages.push(&message_1[..]);
|
|
||||||
keys.push(&aggregate_pubs[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let domain = spec.get_domain(attestation_epoch, Domain::Attestation, &state.fork);
|
|
||||||
|
|
||||||
verify!(
|
verify!(
|
||||||
a.aggregate_signature
|
data.previous_crosslink_root
|
||||||
.verify_multiple(&messages[..], domain, &keys[..]),
|
== Hash256::from_slice(&state.get_previous_crosslink(data.shard)?.tree_hash_root()),
|
||||||
Invalid::BadSignature
|
Invalid::BadPreviousCrosslink
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
invalid!(Invalid::BadTargetEpoch)
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,75 +1,118 @@
|
|||||||
use super::errors::{
|
use super::errors::{
|
||||||
IndexedAttestationInvalid as Invalid, IndexedAttestationValidationError as Error,
|
IndexedAttestationInvalid as Invalid, IndexedAttestationValidationError as Error,
|
||||||
};
|
};
|
||||||
use crate::common::verify_bitfield_length;
|
use std::collections::HashSet;
|
||||||
|
use std::iter::FromIterator;
|
||||||
use tree_hash::TreeHash;
|
use tree_hash::TreeHash;
|
||||||
use types::*;
|
use types::*;
|
||||||
|
|
||||||
/// Indicates if a `IndexedAttestation` is valid to be included in a block in the current epoch of the given
|
/// Verify an `IndexedAttestation`.
|
||||||
/// state.
|
|
||||||
///
|
///
|
||||||
/// Returns `Ok(())` if the `IndexedAttestation` is valid, otherwise indicates the reason for invalidity.
|
/// Spec v0.6.1
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
pub fn verify_indexed_attestation<T: EthSpec>(
|
pub fn verify_indexed_attestation<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
indexed_attestation: &IndexedAttestation,
|
indexed_attestation: &IndexedAttestation,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if indexed_attestation.custody_bitfield.num_set_bits() > 0 {
|
verify_indexed_attestation_parametric(state, indexed_attestation, spec, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify but don't check the signature.
|
||||||
|
///
|
||||||
|
/// Spec v0.6.1
|
||||||
|
pub fn verify_indexed_attestation_without_signature<T: EthSpec>(
|
||||||
|
state: &BeaconState<T>,
|
||||||
|
indexed_attestation: &IndexedAttestation,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
verify_indexed_attestation_parametric(state, indexed_attestation, spec, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optionally check the signature.
|
||||||
|
///
|
||||||
|
/// Spec v0.6.1
|
||||||
|
fn verify_indexed_attestation_parametric<T: EthSpec>(
|
||||||
|
state: &BeaconState<T>,
|
||||||
|
indexed_attestation: &IndexedAttestation,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
verify_signature: bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let custody_bit_0_indices = &indexed_attestation.custody_bit_0_indices;
|
||||||
|
let custody_bit_1_indices = &indexed_attestation.custody_bit_1_indices;
|
||||||
|
|
||||||
|
// Ensure no duplicate indices across custody bits
|
||||||
|
let custody_bit_intersection =
|
||||||
|
&HashSet::from_iter(custody_bit_0_indices) & &HashSet::from_iter(custody_bit_1_indices);
|
||||||
|
verify!(
|
||||||
|
custody_bit_intersection.is_empty(),
|
||||||
|
Invalid::CustodyBitValidatorsIntersect
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that nobody signed with custody bit 1 (to be removed in phase 1)
|
||||||
|
if custody_bit_1_indices.len() > 0 {
|
||||||
invalid!(Invalid::CustodyBitfieldHasSetBits);
|
invalid!(Invalid::CustodyBitfieldHasSetBits);
|
||||||
}
|
}
|
||||||
|
|
||||||
if indexed_attestation.validator_indices.is_empty() {
|
let total_indices = custody_bit_0_indices.len() + custody_bit_1_indices.len();
|
||||||
invalid!(Invalid::NoValidatorIndices);
|
verify!(1 <= total_indices, Invalid::NoValidatorIndices);
|
||||||
}
|
verify!(
|
||||||
|
total_indices as u64 <= spec.max_indices_per_attestation,
|
||||||
|
Invalid::MaxIndicesExceed(spec.max_indices_per_attestation, total_indices)
|
||||||
|
);
|
||||||
|
|
||||||
for i in 0..(indexed_attestation.validator_indices.len() - 1) {
|
// Check that both vectors of indices are sorted
|
||||||
if indexed_attestation.validator_indices[i] >= indexed_attestation.validator_indices[i + 1]
|
let check_sorted = |list: &Vec<u64>| {
|
||||||
{
|
for i in 0..list.len() - 1 {
|
||||||
|
if list[i] >= list[i + 1] {
|
||||||
invalid!(Invalid::BadValidatorIndicesOrdering(i));
|
invalid!(Invalid::BadValidatorIndicesOrdering(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
if !verify_bitfield_length(
|
|
||||||
&indexed_attestation.custody_bitfield,
|
|
||||||
indexed_attestation.validator_indices.len(),
|
|
||||||
) {
|
|
||||||
invalid!(Invalid::BadCustodyBitfieldLength(
|
|
||||||
indexed_attestation.validator_indices.len(),
|
|
||||||
indexed_attestation.custody_bitfield.len()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if indexed_attestation.validator_indices.len() > spec.max_indices_per_indexed_vote as usize {
|
|
||||||
invalid!(Invalid::MaxIndicesExceed(
|
|
||||||
spec.max_indices_per_indexed_vote as usize,
|
|
||||||
indexed_attestation.validator_indices.len()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: this signature verification could likely be replaced with:
|
|
||||||
//
|
|
||||||
// super::validate_attestation::validate_attestation_signature(..)
|
|
||||||
|
|
||||||
let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2];
|
|
||||||
let mut message_exists = vec![false; 2];
|
|
||||||
|
|
||||||
for (i, v) in indexed_attestation.validator_indices.iter().enumerate() {
|
|
||||||
let custody_bit = match indexed_attestation.custody_bitfield.get(i) {
|
|
||||||
Ok(bit) => bit,
|
|
||||||
Err(_) => unreachable!(),
|
|
||||||
};
|
};
|
||||||
|
check_sorted(custody_bit_0_indices)?;
|
||||||
|
check_sorted(custody_bit_1_indices)?;
|
||||||
|
|
||||||
message_exists[custody_bit as usize] = true;
|
if verify_signature {
|
||||||
|
verify_indexed_attestation_signature(state, indexed_attestation, spec)?;
|
||||||
|
}
|
||||||
|
|
||||||
match state.validator_registry.get(*v as usize) {
|
Ok(())
|
||||||
Some(validator) => {
|
}
|
||||||
aggregate_pubs[custody_bit as usize].add(&validator.pubkey);
|
|
||||||
}
|
/// Create an aggregate public key for a list of validators, failing if any key can't be found.
|
||||||
None => invalid!(Invalid::UnknownValidator(*v)),
|
fn create_aggregate_pubkey<'a, T, I>(
|
||||||
};
|
state: &BeaconState<T>,
|
||||||
}
|
validator_indices: I,
|
||||||
|
) -> Result<AggregatePublicKey, Error>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = &'a u64>,
|
||||||
|
T: EthSpec,
|
||||||
|
{
|
||||||
|
validator_indices.into_iter().try_fold(
|
||||||
|
AggregatePublicKey::new(),
|
||||||
|
|mut aggregate_pubkey, &validator_idx| {
|
||||||
|
state
|
||||||
|
.validator_registry
|
||||||
|
.get(validator_idx as usize)
|
||||||
|
.ok_or(Error::Invalid(Invalid::UnknownValidator(validator_idx)))
|
||||||
|
.map(|validator| {
|
||||||
|
aggregate_pubkey.add(&validator.pubkey);
|
||||||
|
aggregate_pubkey
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify the signature of an IndexedAttestation.
|
||||||
|
///
|
||||||
|
/// Spec v0.6.1
|
||||||
|
fn verify_indexed_attestation_signature<T: EthSpec>(
|
||||||
|
state: &BeaconState<T>,
|
||||||
|
indexed_attestation: &IndexedAttestation,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let bit_0_pubkey = create_aggregate_pubkey(state, &indexed_attestation.custody_bit_0_indices)?;
|
||||||
|
let bit_1_pubkey = create_aggregate_pubkey(state, &indexed_attestation.custody_bit_1_indices)?;
|
||||||
|
|
||||||
let message_0 = AttestationDataAndCustodyBit {
|
let message_0 = AttestationDataAndCustodyBit {
|
||||||
data: indexed_attestation.data.clone(),
|
data: indexed_attestation.data.clone(),
|
||||||
@ -85,23 +128,24 @@ pub fn verify_indexed_attestation<T: EthSpec>(
|
|||||||
let mut messages = vec![];
|
let mut messages = vec![];
|
||||||
let mut keys = vec![];
|
let mut keys = vec![];
|
||||||
|
|
||||||
if message_exists[0] {
|
if !indexed_attestation.custody_bit_0_indices.is_empty() {
|
||||||
messages.push(&message_0[..]);
|
messages.push(&message_0[..]);
|
||||||
keys.push(&aggregate_pubs[0]);
|
keys.push(&bit_0_pubkey);
|
||||||
}
|
}
|
||||||
if message_exists[1] {
|
if !indexed_attestation.custody_bit_1_indices.is_empty() {
|
||||||
messages.push(&message_1[..]);
|
messages.push(&message_1[..]);
|
||||||
keys.push(&aggregate_pubs[1]);
|
keys.push(&bit_1_pubkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
let domain = {
|
let domain = spec.get_domain(
|
||||||
let epoch = indexed_attestation.data.slot.epoch(spec.slots_per_epoch);
|
indexed_attestation.data.target_epoch,
|
||||||
spec.get_domain(epoch, Domain::Attestation, &state.fork)
|
Domain::Attestation,
|
||||||
};
|
&state.fork,
|
||||||
|
);
|
||||||
|
|
||||||
verify!(
|
verify!(
|
||||||
indexed_attestation
|
indexed_attestation
|
||||||
.aggregate_signature
|
.signature
|
||||||
.verify_multiple(&messages[..], domain, &keys[..]),
|
.verify_multiple(&messages[..], domain, &keys[..]),
|
||||||
Invalid::BadSignature
|
Invalid::BadSignature
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::test_utils::TestRandom;
|
use crate::test_utils::TestRandom;
|
||||||
use crate::{Attestation, AttestationData, Bitfield};
|
use crate::{AttestationData, Bitfield};
|
||||||
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use ssz_derive::{Decode, Encode};
|
use ssz_derive::{Decode, Encode};
|
||||||
@ -28,22 +28,6 @@ pub struct PendingAttestation {
|
|||||||
pub proposer_index: u64,
|
pub proposer_index: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PendingAttestation {
|
|
||||||
/// Create a `PendingAttestation` from an `Attestation`.
|
|
||||||
pub fn from_attestation(
|
|
||||||
attestation: &Attestation,
|
|
||||||
inclusion_delay: u64,
|
|
||||||
proposer_index: u64,
|
|
||||||
) -> Self {
|
|
||||||
PendingAttestation {
|
|
||||||
data: attestation.data.clone(),
|
|
||||||
aggregation_bitfield: attestation.aggregation_bitfield.clone(),
|
|
||||||
inclusion_delay,
|
|
||||||
proposer_index,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
Loading…
Reference in New Issue
Block a user