mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-10 04:01:51 +00:00
First compiling version of per-block-proc refactor
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
use crate::errors::{AttestationInvalid as Invalid, AttestationValidationError as Error};
|
||||
use ssz::TreeHash;
|
||||
use types::beacon_state::helpers::*;
|
||||
use types::*;
|
||||
|
||||
/// Validate an attestation, checking the aggregate signature.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn validate_attestation(
|
||||
state: &BeaconState,
|
||||
attestation: &Attestation,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
validate_attestation_signature_optional(state, attestation, spec, true)
|
||||
}
|
||||
|
||||
/// Validate an attestation, without checking the aggregate signature.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn validate_attestation_without_signature(
|
||||
state: &BeaconState,
|
||||
attestation: &Attestation,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
validate_attestation_signature_optional(state, attestation, spec, false)
|
||||
}
|
||||
|
||||
/// Validate an attestation, optionally checking the aggregate signature.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
fn validate_attestation_signature_optional(
|
||||
state: &BeaconState,
|
||||
attestation: &Attestation,
|
||||
spec: &ChainSpec,
|
||||
verify_signature: bool,
|
||||
) -> Result<(), Error> {
|
||||
// Verify that `attestation.data.slot >= GENESIS_SLOT`.
|
||||
verify!(
|
||||
attestation.data.slot >= spec.genesis_slot,
|
||||
Invalid::PreGenesis
|
||||
);
|
||||
|
||||
// Verify that `attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot`.
|
||||
verify!(
|
||||
attestation.data.slot + spec.min_attestation_inclusion_delay <= state.slot,
|
||||
Invalid::IncludedTooEarly
|
||||
);
|
||||
|
||||
// Verify that `state.slot < attestation.data.slot + SLOTS_PER_EPOCH`.
|
||||
verify!(
|
||||
state.slot < attestation.data.slot + spec.slots_per_epoch,
|
||||
Invalid::IncludedTooLate
|
||||
);
|
||||
|
||||
// Verify that `attestation.data.justified_epoch` is equal to `state.justified_epoch` if
|
||||
// `slot_to_epoch(attestation.data.slot + 1) >= get_current_epoch(state) else
|
||||
// state.previous_justified_epoch`.
|
||||
if (attestation.data.slot + 1).epoch(spec.slots_per_epoch) >= state.current_epoch(spec) {
|
||||
verify!(
|
||||
attestation.data.justified_epoch == state.justified_epoch,
|
||||
Invalid::WrongJustifiedSlot
|
||||
);
|
||||
} else {
|
||||
verify!(
|
||||
attestation.data.justified_epoch == state.previous_justified_epoch,
|
||||
Invalid::WrongJustifiedSlot
|
||||
);
|
||||
}
|
||||
|
||||
// Verify that `attestation.data.justified_block_root` is equal to `get_block_root(state,
|
||||
// get_epoch_start_slot(attestation.data.justified_epoch))`.
|
||||
verify!(
|
||||
attestation.data.justified_block_root
|
||||
== *state
|
||||
.get_block_root(
|
||||
attestation
|
||||
.data
|
||||
.justified_epoch
|
||||
.start_slot(spec.slots_per_epoch),
|
||||
&spec
|
||||
)
|
||||
.ok_or(BeaconStateError::InsufficientBlockRoots)?,
|
||||
Invalid::WrongJustifiedRoot
|
||||
);
|
||||
|
||||
// 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.latest_crosslink
|
||||
== state.latest_crosslinks[attestation.data.shard as usize])
|
||||
| (state.latest_crosslinks[attestation.data.shard as usize] == potential_crosslink),
|
||||
Invalid::BadLatestCrosslinkRoot
|
||||
);
|
||||
|
||||
// Get the committee for this attestation
|
||||
let (committee, _shard) = state
|
||||
.get_crosslink_committees_at_slot(attestation.data.slot, spec)?
|
||||
.iter()
|
||||
.find(|(_committee, shard)| *shard == attestation.data.shard)
|
||||
.ok_or_else(|| Error::Invalid(Invalid::NoCommitteeForShard))?;
|
||||
|
||||
// Custody bitfield is all zeros (phase 0 requirement).
|
||||
verify!(
|
||||
attestation.custody_bitfield.num_set_bits() == 0,
|
||||
Invalid::CustodyBitfieldHasSetBits
|
||||
);
|
||||
// Custody bitfield length is correct.
|
||||
verify!(
|
||||
verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()),
|
||||
Invalid::BadCustodyBitfieldLength
|
||||
);
|
||||
// Aggregation bitfield isn't empty.
|
||||
verify!(
|
||||
attestation.aggregation_bitfield.num_set_bits() != 0,
|
||||
Invalid::AggregationBitfieldIsEmpty
|
||||
);
|
||||
// Aggregation bitfield length is correct.
|
||||
verify!(
|
||||
verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()),
|
||||
Invalid::BadAggregationBitfieldLength
|
||||
);
|
||||
|
||||
if verify_signature {
|
||||
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
|
||||
verify!(
|
||||
verify_attestation_signature(
|
||||
state,
|
||||
committee,
|
||||
attestation_epoch,
|
||||
&attestation.custody_bitfield,
|
||||
&attestation.data,
|
||||
&attestation.aggregate_signature,
|
||||
spec
|
||||
),
|
||||
Invalid::BadSignature
|
||||
);
|
||||
}
|
||||
|
||||
// [TO BE REMOVED IN PHASE 1] Verify that `attestation.data.crosslink_data_root == ZERO_HASH`.
|
||||
verify!(
|
||||
attestation.data.crosslink_data_root == spec.zero_hash,
|
||||
Invalid::ShardBlockRootNotZero
|
||||
);
|
||||
|
||||
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.4.0
|
||||
fn verify_attestation_signature(
|
||||
state: &BeaconState,
|
||||
committee: &[usize],
|
||||
attestation_epoch: Epoch,
|
||||
custody_bitfield: &Bitfield,
|
||||
attestation_data: &AttestationData,
|
||||
aggregate_signature: &AggregateSignature,
|
||||
spec: &ChainSpec,
|
||||
) -> bool {
|
||||
let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2];
|
||||
let mut message_exists = vec![false; 2];
|
||||
|
||||
for (i, v) in committee.iter().enumerate() {
|
||||
let custody_bit = match custody_bitfield.get(i) {
|
||||
Ok(bit) => bit,
|
||||
// Invalidate signature if custody_bitfield.len() < committee
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
// Invalidate signature if validator index is unknown.
|
||||
None => return false,
|
||||
};
|
||||
}
|
||||
|
||||
// Message when custody bitfield is `false`
|
||||
let message_0 = AttestationDataAndCustodyBit {
|
||||
data: attestation_data.clone(),
|
||||
custody_bit: false,
|
||||
}
|
||||
.hash_tree_root();
|
||||
|
||||
// Message when custody bitfield is `true`
|
||||
let message_1 = AttestationDataAndCustodyBit {
|
||||
data: attestation_data.clone(),
|
||||
custody_bit: true,
|
||||
}
|
||||
.hash_tree_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);
|
||||
|
||||
aggregate_signature.verify_multiple(&messages[..], domain, &keys[..])
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
use super::verify_slashable_attestation::verify_slashable_attestation;
|
||||
use crate::errors::{AttesterSlashingInvalid as Invalid, AttesterSlashingValidationError as Error};
|
||||
use types::*;
|
||||
|
||||
/// Returns `Ok(())` if some `AttesterSlashing` is valid to be included in some `BeaconState`,
|
||||
/// otherwise returns an `Err`.
|
||||
///
|
||||
/// Returns the slashable indices from the `AttesterSlashing`.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn verify_attester_slashing(
|
||||
state: &BeaconState,
|
||||
attester_slashing: &AttesterSlashing,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Vec<u64>, Error> {
|
||||
let slashable_attestation_1 = &attester_slashing.slashable_attestation_1;
|
||||
let slashable_attestation_2 = &attester_slashing.slashable_attestation_2;
|
||||
|
||||
verify!(
|
||||
slashable_attestation_1.data != slashable_attestation_2.data,
|
||||
Invalid::AttestationDataIdentical
|
||||
);
|
||||
verify!(
|
||||
slashable_attestation_1.is_double_vote(slashable_attestation_2, spec)
|
||||
| slashable_attestation_1.is_surround_vote(slashable_attestation_2, spec),
|
||||
Invalid::NotSlashable
|
||||
);
|
||||
|
||||
verify_slashable_attestation(state, &slashable_attestation_1, spec)
|
||||
.map_err(|e| Error::Invalid(Invalid::SlashableAttestation1Invalid(e.into())))?;
|
||||
verify_slashable_attestation(state, &slashable_attestation_2, spec)
|
||||
.map_err(|e| Error::Invalid(Invalid::SlashableAttestation2Invalid(e.into())))?;
|
||||
|
||||
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::Invalid(Invalid::UnknownValidator))?;
|
||||
|
||||
if slashable_attestation_1.validator_indices.contains(&i) & !validator.slashed {
|
||||
slashable_indices.push(*i);
|
||||
}
|
||||
}
|
||||
|
||||
verify!(!slashable_indices.is_empty(), Invalid::NoSlashableIndices);
|
||||
|
||||
Ok(slashable_indices)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use crate::errors::{DepositInvalid as Invalid, DepositValidationError as Error};
|
||||
use ssz::TreeHash;
|
||||
use types::beacon_state::helpers::verify_bitfield_length;
|
||||
use types::*;
|
||||
|
||||
/// Verify validity of ``slashable_attestation`` fields.
|
||||
///
|
||||
/// Returns `Ok(())` if all fields are valid.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn verify_deposit(
|
||||
state: &BeaconState,
|
||||
deposit: &Deposit,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
// TODO: verify serialized deposit data.
|
||||
|
||||
// TODO: verify deposit index.
|
||||
verify!(deposit.index == state.deposit_index, Invalid::BadIndex);
|
||||
|
||||
// TODO: verify merkle branch.
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
use crate::errors::{ExitInvalid as Invalid, ExitValidationError as Error};
|
||||
use ssz::SignedRoot;
|
||||
use types::*;
|
||||
|
||||
/// Verify validity of ``slashable_attestation`` fields.
|
||||
///
|
||||
/// Returns `Ok(())` if all fields are valid.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn verify_exit(
|
||||
state: &BeaconState,
|
||||
exit: &VoluntaryExit,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let validator = state
|
||||
.validator_registry
|
||||
.get(exit.validator_index as usize)
|
||||
.ok_or(Error::Invalid(Invalid::ValidatorUnknown))?;
|
||||
|
||||
verify!(
|
||||
validator.exit_epoch
|
||||
> state.get_delayed_activation_exit_epoch(state.current_epoch(spec), spec),
|
||||
Invalid::AlreadyExited
|
||||
);
|
||||
|
||||
verify!(
|
||||
state.current_epoch(spec) >= exit.epoch,
|
||||
Invalid::FutureEpoch
|
||||
);
|
||||
|
||||
let message = exit.signed_root();
|
||||
let domain = spec.get_domain(exit.epoch, Domain::Exit, &state.fork);
|
||||
|
||||
verify!(
|
||||
exit.signature
|
||||
.verify(&message[..], domain, &validator.pubkey),
|
||||
Invalid::BadSignature
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
use crate::errors::{ProposerSlashingInvalid as Invalid, ProposerSlashingValidationError as Error};
|
||||
use ssz::SignedRoot;
|
||||
use types::*;
|
||||
|
||||
/// Returns `Ok(())` if some `ProposerSlashing` is valid to be included in some `BeaconState`,
|
||||
/// otherwise returns an `Err`.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn verify_proposer_slashing(
|
||||
proposer_slashing: &ProposerSlashing,
|
||||
state: &BeaconState,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let proposer = state
|
||||
.validator_registry
|
||||
.get(proposer_slashing.proposer_index as usize)
|
||||
.ok_or(Error::Invalid(Invalid::ProposerUnknown))?;
|
||||
|
||||
verify!(
|
||||
proposer_slashing.proposal_1.slot == proposer_slashing.proposal_2.slot,
|
||||
Invalid::ProposalSlotMismatch
|
||||
);
|
||||
|
||||
verify!(
|
||||
proposer_slashing.proposal_1.shard == proposer_slashing.proposal_2.shard,
|
||||
Invalid::ProposalShardMismatch
|
||||
);
|
||||
|
||||
verify!(
|
||||
proposer_slashing.proposal_1.block_root != proposer_slashing.proposal_2.block_root,
|
||||
Invalid::ProposalBlockRootMismatch
|
||||
);
|
||||
|
||||
verify!(!proposer.slashed, Invalid::ProposerAlreadySlashed);
|
||||
|
||||
verify!(
|
||||
verify_proposal_signature(
|
||||
&proposer_slashing.proposal_1,
|
||||
&proposer.pubkey,
|
||||
&state.fork,
|
||||
spec
|
||||
),
|
||||
Invalid::BadProposal1Signature
|
||||
);
|
||||
verify!(
|
||||
verify_proposal_signature(
|
||||
&proposer_slashing.proposal_2,
|
||||
&proposer.pubkey,
|
||||
&state.fork,
|
||||
spec
|
||||
),
|
||||
Invalid::BadProposal2Signature
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_proposal_signature(
|
||||
proposal: &Proposal,
|
||||
pubkey: &PublicKey,
|
||||
fork: &Fork,
|
||||
spec: &ChainSpec,
|
||||
) -> bool {
|
||||
let message = proposal.signed_root();
|
||||
let domain = spec.get_domain(
|
||||
proposal.slot.epoch(spec.slots_per_epoch),
|
||||
Domain::Proposal,
|
||||
fork,
|
||||
);
|
||||
proposal.signature.verify(&message[..], domain, pubkey)
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
use crate::errors::{
|
||||
SlashableAttestationInvalid as Invalid, SlashableAttestationValidationError as Error,
|
||||
};
|
||||
use ssz::TreeHash;
|
||||
use types::beacon_state::helpers::verify_bitfield_length;
|
||||
use types::*;
|
||||
|
||||
/// Verify validity of ``slashable_attestation`` fields.
|
||||
///
|
||||
/// Returns `Ok(())` if all fields are valid.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn verify_slashable_attestation(
|
||||
state: &BeaconState,
|
||||
slashable_attestation: &SlashableAttestation,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
if slashable_attestation.custody_bitfield.num_set_bits() > 0 {
|
||||
invalid!(Invalid::CustodyBitfieldHasSetBits);
|
||||
}
|
||||
|
||||
if slashable_attestation.validator_indices.is_empty() {
|
||||
invalid!(Invalid::NoValidatorIndices);
|
||||
}
|
||||
|
||||
for i in 0..(slashable_attestation.validator_indices.len() - 1) {
|
||||
if slashable_attestation.validator_indices[i]
|
||||
>= slashable_attestation.validator_indices[i + 1]
|
||||
{
|
||||
invalid!(Invalid::BadValidatorIndicesOrdering);
|
||||
}
|
||||
}
|
||||
|
||||
if !verify_bitfield_length(
|
||||
&slashable_attestation.custody_bitfield,
|
||||
slashable_attestation.validator_indices.len(),
|
||||
) {
|
||||
invalid!(Invalid::BadCustodyBitfieldLength);
|
||||
}
|
||||
|
||||
if slashable_attestation.validator_indices.len() > spec.max_indices_per_slashable_vote as usize
|
||||
{
|
||||
invalid!(Invalid::MaxIndicesExceed);
|
||||
}
|
||||
|
||||
// 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 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 state.validator_registry.get(*v as usize) {
|
||||
Some(validator) => {
|
||||
aggregate_pubs[custody_bit as usize].add(&validator.pubkey);
|
||||
}
|
||||
None => invalid!(Invalid::UnknownValidator),
|
||||
};
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
let domain = {
|
||||
let epoch = slashable_attestation.data.slot.epoch(spec.slots_per_epoch);
|
||||
spec.get_domain(epoch, Domain::Attestation, &state.fork)
|
||||
};
|
||||
|
||||
verify!(
|
||||
slashable_attestation
|
||||
.aggregate_signature
|
||||
.verify_multiple(&messages[..], domain, &keys[..]),
|
||||
Invalid::BadSignature
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use crate::errors::{TransferInvalid as Invalid, TransferValidationError as Error};
|
||||
use ssz::TreeHash;
|
||||
use types::beacon_state::helpers::verify_bitfield_length;
|
||||
use types::*;
|
||||
|
||||
/// Verify validity of ``slashable_attestation`` fields.
|
||||
///
|
||||
/// Returns `Ok(())` if all fields are valid.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn verify_transfer(
|
||||
state: &BeaconState,
|
||||
transfer: &Transfer,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
// TODO: verify transfer.
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user