mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-19 21:04:41 +00:00
Add untested attestation validation logic
This commit is contained in:
34
beacon_chain/attestation_validation/src/enums.rs
Normal file
34
beacon_chain/attestation_validation/src/enums.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
/// Reasons why an `AttestationRecord` can be invalid.
|
||||
pub enum Invalid {
|
||||
AttestationTooRecent,
|
||||
AttestationTooOld,
|
||||
JustifiedSlotImpermissable,
|
||||
JustifiedBlockNotInChain,
|
||||
JustifiedBlockHashMismatch,
|
||||
UnknownShard,
|
||||
ShardBlockHashMismatch,
|
||||
SignatureInvalid,
|
||||
}
|
||||
|
||||
/// The outcome of validating the `AttestationRecord`.
|
||||
///
|
||||
/// Distinct from the `Error` enum as an `Outcome` indicates that validation executed sucessfully
|
||||
/// and determined the validity `AttestationRecord`.
|
||||
pub enum Outcome {
|
||||
Valid,
|
||||
Invalid(Invalid),
|
||||
}
|
||||
|
||||
/// Errors that prevent this function from correctly validating the `AttestationRecord`.
|
||||
///
|
||||
/// Distinct from the `Outcome` enum as `Errors` indicate that validation encountered an unexpected
|
||||
/// condition and was unable to perform its duty.
|
||||
pub enum Error {
|
||||
BlockHasNoParent,
|
||||
BadValidatorIndex,
|
||||
UnableToLookupBlockAtSlot,
|
||||
OutOfBoundsBitfieldIndex,
|
||||
PublicKeyCorrupt,
|
||||
NoPublicKeyForValidator,
|
||||
DBError(String),
|
||||
}
|
||||
18
beacon_chain/attestation_validation/src/lib.rs
Normal file
18
beacon_chain/attestation_validation/src/lib.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
extern crate bls;
|
||||
extern crate db;
|
||||
extern crate hashing;
|
||||
extern crate ssz;
|
||||
extern crate ssz_helpers;
|
||||
extern crate types;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
mod enums;
|
||||
mod validate_for_block;
|
||||
mod validate_for_state;
|
||||
mod validate_signature;
|
||||
|
||||
pub use enums::{Invalid, Outcome, Error};
|
||||
pub use validate_for_block::validate_attestation_for_block;
|
||||
pub use validate_for_state::validate_attestation_data_for_state;
|
||||
pub use validate_signature::validate_attestation_signature;
|
||||
19
beacon_chain/attestation_validation/src/macros.rs
Normal file
19
beacon_chain/attestation_validation/src/macros.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
macro_rules! verify_or {
|
||||
($condition: expr, $result: expr) => {
|
||||
if !$condition {
|
||||
$result
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! reject {
|
||||
($result: expr) => {
|
||||
return Ok(Outcome::Invalid($result));
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! accept {
|
||||
() => {
|
||||
Ok(Outcome::Valid)
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
use super::{Error, Invalid, Outcome};
|
||||
|
||||
/// Check that an attestation is valid to be included in some block.
|
||||
pub fn validate_attestation_for_block<T>(
|
||||
attestation_slot: u64,
|
||||
block_slot: u64,
|
||||
parent_block_slot: u64,
|
||||
min_attestation_inclusion_delay: u64,
|
||||
epoch_length: u64,
|
||||
) -> Result<Outcome, Error> {
|
||||
/*
|
||||
* There is a delay before an attestation may be included in a block, quantified by
|
||||
* `slots` and defined as `min_attestation_inclusion_delay`.
|
||||
*
|
||||
* So, an attestation must be at least `min_attestation_inclusion_delay` slots "older" than the
|
||||
* block it is contained in.
|
||||
*/
|
||||
verify_or!(
|
||||
attestation_slot <= block_slot.saturating_sub(min_attestation_inclusion_delay),
|
||||
reject!(Invalid::AttestationTooRecent)
|
||||
);
|
||||
|
||||
/*
|
||||
* A block may not include attestations reference slots more than an epoch length + 1 prior to
|
||||
* the block slot.
|
||||
*/
|
||||
verify_or!(
|
||||
attestation_slot >= parent_block_slot.saturating_sub(epoch_length + 1),
|
||||
reject!(Invalid::AttestationTooOld)
|
||||
);
|
||||
|
||||
accept!()
|
||||
}
|
||||
107
beacon_chain/attestation_validation/src/validate_for_state.rs
Normal file
107
beacon_chain/attestation_validation/src/validate_for_state.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
use super::db::stores::{BeaconBlockAtSlotError, BeaconBlockStore};
|
||||
use super::db::ClientDB;
|
||||
use super::types::Hash256;
|
||||
use super::types::{AttestationData, BeaconState};
|
||||
use super::{Error, Invalid, Outcome};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Check that an attestation is valid with reference to some state.
|
||||
pub fn validate_attestation_data_for_state<T>(
|
||||
data: &AttestationData,
|
||||
chain_tip_block_hash: &Hash256,
|
||||
state: &BeaconState,
|
||||
block_store: &Arc<BeaconBlockStore<T>>,
|
||||
) -> Result<Outcome, Error>
|
||||
where
|
||||
T: ClientDB + Sized,
|
||||
{
|
||||
/*
|
||||
* The attestation's `justified_slot` must be the same as the last justified slot known to this
|
||||
* client.
|
||||
*
|
||||
* In the case that an attestation references a slot _before_ the latest state transition, it
|
||||
* is acceptable for the attestation to reference the previous known `justified_slot`. If this
|
||||
* were not the case, all attestations created _prior_ to the last state recalculation would be
|
||||
* rejected if a block was justified in that state recalculation. It is both ideal and likely
|
||||
* that blocks will be justified during a state recalcuation.
|
||||
*/
|
||||
{
|
||||
let permissable_justified_slot = if data.slot >= state.latest_state_recalculation_slot {
|
||||
state.justified_slot
|
||||
} else {
|
||||
state.previous_justified_slot
|
||||
};
|
||||
verify_or!(
|
||||
data.justified_slot == permissable_justified_slot,
|
||||
reject!(Invalid::JustifiedSlotImpermissable)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* The `justified_block_hash` in the attestation must match exactly the hash of the block at
|
||||
* that slot in the local chain.
|
||||
*
|
||||
* This condition also infers that the `justified_slot` specified in attestation must exist
|
||||
* locally.
|
||||
*/
|
||||
match block_hash_at_slot(chain_tip_block_hash, data.justified_slot, block_store)? {
|
||||
None => reject!(Invalid::JustifiedBlockNotInChain),
|
||||
Some(local_justified_block_hash) => {
|
||||
verify_or!(
|
||||
data.justified_block_hash == local_justified_block_hash,
|
||||
reject!(Invalid::JustifiedBlockHashMismatch)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* The `shard_block_hash` in the state's `latest_crosslinks` must match either the
|
||||
* `latest_crosslink_hash` or the `shard_block_hash` on the attestation.
|
||||
*
|
||||
* TODO: figure out the reasoning behind this.
|
||||
*/
|
||||
match state.latest_crosslinks.get(data.shard as usize) {
|
||||
None => reject!(Invalid::UnknownShard),
|
||||
Some(crosslink) => {
|
||||
let local_shard_block_hash = crosslink.shard_block_hash;
|
||||
let shard_block_hash_is_permissable = {
|
||||
(local_shard_block_hash == data.latest_crosslink_hash)
|
||||
|| (local_shard_block_hash == data.shard_block_hash)
|
||||
};
|
||||
verify_or!(
|
||||
shard_block_hash_is_permissable,
|
||||
reject!(Invalid::ShardBlockHashMismatch)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
accept!()
|
||||
}
|
||||
|
||||
/// Returns the hash (or None) of a block at a slot in the chain that is specified by
|
||||
/// `chain_tip_hash`.
|
||||
///
|
||||
/// Given that the database stores multiple chains, it is possible for there to be multiple blocks
|
||||
/// at the given slot. `chain_tip_hash` specifies exactly which chain should be used.
|
||||
fn block_hash_at_slot<T>(
|
||||
chain_tip_hash: &Hash256,
|
||||
slot: u64,
|
||||
block_store: &Arc<BeaconBlockStore<T>>,
|
||||
) -> Result<Option<Hash256>, Error>
|
||||
where
|
||||
T: ClientDB + Sized,
|
||||
{
|
||||
match block_store.block_at_slot(&chain_tip_hash, slot)? {
|
||||
None => Ok(None),
|
||||
Some((hash_bytes, _)) => Ok(Some(Hash256::from(&hash_bytes[..]))),
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BeaconBlockAtSlotError> for Error {
|
||||
fn from(e: BeaconBlockAtSlotError) -> Self {
|
||||
match e {
|
||||
BeaconBlockAtSlotError::DBError(s) => Error::DBError(s),
|
||||
_ => Error::UnableToLookupBlockAtSlot,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
use super::bls::{AggregatePublicKey, AggregateSignature};
|
||||
use super::db::stores::{ValidatorStore, ValidatorStoreError};
|
||||
use super::db::ClientDB;
|
||||
use super::types::{AttestationData, Bitfield, BitfieldError};
|
||||
use super::{Error, Invalid, Outcome};
|
||||
|
||||
/// Validate that some signature is correct for some attestation data and known validator set.
|
||||
pub fn validate_attestation_signature<T>(
|
||||
attestation_data: &AttestationData,
|
||||
participation_bitfield: &Bitfield,
|
||||
aggregate_signature: &AggregateSignature,
|
||||
attestation_indices: &[usize],
|
||||
validator_store: &ValidatorStore<T>,
|
||||
) -> Result<Outcome, Error>
|
||||
where
|
||||
T: ClientDB + Sized,
|
||||
{
|
||||
let mut agg_pub_key = AggregatePublicKey::new();
|
||||
|
||||
for i in 0..attestation_indices.len() {
|
||||
let voted = participation_bitfield.get(i)?;
|
||||
if voted {
|
||||
// De-reference the attestation index into a canonical ValidatorRecord index.
|
||||
let validator = *attestation_indices.get(i).ok_or(Error::BadValidatorIndex)?;
|
||||
// Load the public key.
|
||||
let pub_key = validator_store
|
||||
.get_public_key_by_index(validator)?
|
||||
.ok_or(Error::NoPublicKeyForValidator)?;
|
||||
// Aggregate the public key.
|
||||
agg_pub_key.add(&pub_key);
|
||||
}
|
||||
}
|
||||
|
||||
let signed_message = attestation_data_signing_message(attestation_data);
|
||||
verify_or!(
|
||||
// TODO: ensure "domain" for aggregate signatures is included.
|
||||
// https://github.com/sigp/lighthouse/issues/91
|
||||
aggregate_signature.verify(&signed_message, &agg_pub_key),
|
||||
reject!(Invalid::SignatureInvalid)
|
||||
);
|
||||
|
||||
accept!()
|
||||
}
|
||||
|
||||
fn attestation_data_signing_message(attestation_data: &AttestationData) -> Vec<u8> {
|
||||
let mut signed_message = attestation_data.canonical_root().to_vec();
|
||||
signed_message.append(&mut vec![0]);
|
||||
signed_message
|
||||
}
|
||||
|
||||
impl From<ValidatorStoreError> for Error {
|
||||
fn from(error: ValidatorStoreError) -> Self {
|
||||
match error {
|
||||
ValidatorStoreError::DBError(s) => Error::DBError(s),
|
||||
ValidatorStoreError::DecodeError => Error::PublicKeyCorrupt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BitfieldError> for Error {
|
||||
fn from(_error: BitfieldError) -> Self {
|
||||
Error::OutOfBoundsBitfieldIndex
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user