Add untested attestation validation logic
This commit is contained in:
parent
d4b6d81c9d
commit
3533b8b892
@ -32,6 +32,8 @@ name = "lighthouse"
|
|||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
"beacon_chain/attestation_validation",
|
||||||
|
"beacon_chain/block_validation",
|
||||||
"beacon_chain/chain",
|
"beacon_chain/chain",
|
||||||
"beacon_chain/naive_fork_choice",
|
"beacon_chain/naive_fork_choice",
|
||||||
"beacon_chain/state-transition",
|
"beacon_chain/state-transition",
|
||||||
@ -45,7 +47,6 @@ members = [
|
|||||||
"beacon_chain/utils/ssz",
|
"beacon_chain/utils/ssz",
|
||||||
"beacon_chain/utils/ssz_helpers",
|
"beacon_chain/utils/ssz_helpers",
|
||||||
"beacon_chain/utils/vec_shuffle",
|
"beacon_chain/utils/vec_shuffle",
|
||||||
"beacon_chain/validation",
|
|
||||||
"beacon_chain/validator_change",
|
"beacon_chain/validator_change",
|
||||||
"beacon_chain/validator_induction",
|
"beacon_chain/validator_induction",
|
||||||
"beacon_chain/validator_shuffling",
|
"beacon_chain/validator_shuffling",
|
||||||
|
12
beacon_chain/attestation_validation/Cargo.toml
Normal file
12
beacon_chain/attestation_validation/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "attestation_validation"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bls = { path = "../utils/bls" }
|
||||||
|
db = { path = "../../lighthouse/db" }
|
||||||
|
hashing = { path = "../utils/hashing" }
|
||||||
|
ssz = { path = "../utils/ssz" }
|
||||||
|
ssz_helpers = { path = "../utils/ssz_helpers" }
|
||||||
|
types = { path = "../types" }
|
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
|
||||||
|
}
|
||||||
|
}
|
14
beacon_chain/block_validation/Cargo.toml
Normal file
14
beacon_chain/block_validation/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "block_validation"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
attestation_validation = { path = "../attestation_validation" }
|
||||||
|
bls = { path = "../utils/bls" }
|
||||||
|
db = { path = "../../lighthouse/db" }
|
||||||
|
hashing = { path = "../utils/hashing" }
|
||||||
|
rayon = "1.0.2"
|
||||||
|
ssz = { path = "../utils/ssz" }
|
||||||
|
ssz_helpers = { path = "../utils/ssz_helpers" }
|
||||||
|
types = { path = "../types" }
|
371
beacon_chain/block_validation/src/block_validation.rs
Normal file
371
beacon_chain/block_validation/src/block_validation.rs
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
extern crate rayon;
|
||||||
|
|
||||||
|
use self::rayon::prelude::*;
|
||||||
|
|
||||||
|
use super::attestation_validation::{AttestationValidationContext, AttestationValidationError};
|
||||||
|
use super::db::stores::{BeaconBlockStore, PoWChainStore, ValidatorStore};
|
||||||
|
use super::db::{ClientDB, DBError};
|
||||||
|
use super::ssz::{Decodable, DecodeError};
|
||||||
|
use super::ssz_helpers::attestation_ssz_splitter::{
|
||||||
|
split_all_attestations, split_one_attestation, AttestationSplitError,
|
||||||
|
};
|
||||||
|
use super::ssz_helpers::ssz_beacon_block::{SszBeaconBlock, SszBeaconBlockError};
|
||||||
|
use super::types::Hash256;
|
||||||
|
use super::types::{AttestationRecord, AttesterMap, BeaconBlock, ProposerMap};
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum SszBeaconBlockValidationError {
|
||||||
|
FutureSlot,
|
||||||
|
SlotAlreadyFinalized,
|
||||||
|
UnknownPoWChainRef,
|
||||||
|
UnknownParentHash,
|
||||||
|
BadAttestationSsz,
|
||||||
|
BadAncestorHashesSsz,
|
||||||
|
BadSpecialsSsz,
|
||||||
|
ParentSlotHigherThanBlockSlot,
|
||||||
|
AttestationValidationError(AttestationValidationError),
|
||||||
|
AttestationSignatureFailed,
|
||||||
|
ProposerAttestationHasObliqueHashes,
|
||||||
|
NoProposerSignature,
|
||||||
|
BadProposerMap,
|
||||||
|
RwLockPoisoned,
|
||||||
|
DBError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The context against which a block should be validated.
|
||||||
|
pub struct BeaconBlockValidationContext<T>
|
||||||
|
where
|
||||||
|
T: ClientDB + Sized,
|
||||||
|
{
|
||||||
|
/// The slot as determined by the system time.
|
||||||
|
pub present_slot: u64,
|
||||||
|
/// The cycle_length as determined by the chain configuration.
|
||||||
|
pub cycle_length: u8,
|
||||||
|
/// The last justified slot as per the client's view of the canonical chain.
|
||||||
|
pub last_justified_slot: u64,
|
||||||
|
/// The last justified block hash as per the client's view of the canonical chain.
|
||||||
|
pub last_justified_block_hash: Hash256,
|
||||||
|
/// The last finalized slot as per the client's view of the canonical chain.
|
||||||
|
pub last_finalized_slot: u64,
|
||||||
|
/// A vec of the hashes of the blocks preceeding the present slot.
|
||||||
|
pub recent_block_hashes: Arc<Vec<Hash256>>,
|
||||||
|
/// A map of slots to a block proposer validation index.
|
||||||
|
pub proposer_map: Arc<ProposerMap>,
|
||||||
|
/// A map of (slot, shard_id) to the attestation set of validation indices.
|
||||||
|
pub attester_map: Arc<AttesterMap>,
|
||||||
|
/// The store containing block information.
|
||||||
|
pub block_store: Arc<BeaconBlockStore<T>>,
|
||||||
|
/// The store containing validator information.
|
||||||
|
pub validator_store: Arc<ValidatorStore<T>>,
|
||||||
|
/// The store containing information about the proof-of-work chain.
|
||||||
|
pub pow_store: Arc<PoWChainStore<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> BeaconBlockValidationContext<T>
|
||||||
|
where
|
||||||
|
T: ClientDB,
|
||||||
|
{
|
||||||
|
/// Validate some SszBeaconBlock against a block validation context. An SszBeaconBlock varies from a BeaconBlock in
|
||||||
|
/// that is a read-only structure that reads directly from encoded SSZ.
|
||||||
|
///
|
||||||
|
/// The reason to validate an SzzBeaconBlock is to avoid decoding it in its entirety if there is
|
||||||
|
/// a suspicion that the block might be invalid. Such a suspicion should be applied to
|
||||||
|
/// all blocks coming from the network.
|
||||||
|
///
|
||||||
|
/// This function will determine if the block is new, already known or invalid (either
|
||||||
|
/// intrinsically or due to some application error.)
|
||||||
|
///
|
||||||
|
/// Note: this function does not implement randao_reveal checking as it is not in the
|
||||||
|
/// specification.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn validate_ssz_block(
|
||||||
|
&self,
|
||||||
|
b: &SszBeaconBlock,
|
||||||
|
) -> Result<BeaconBlock, SszBeaconBlockValidationError>
|
||||||
|
where
|
||||||
|
T: ClientDB + Sized,
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* If the block slot corresponds to a slot in the future, return immediately with an error.
|
||||||
|
*
|
||||||
|
* It is up to the calling fn to determine what should be done with "future" blocks (e.g.,
|
||||||
|
* cache or discard).
|
||||||
|
*/
|
||||||
|
let block_slot = b.slot();
|
||||||
|
if block_slot > self.present_slot {
|
||||||
|
return Err(SszBeaconBlockValidationError::FutureSlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the block is unknown (assumed unknown because we checked the db earlier in this
|
||||||
|
* function) and it comes from a slot that is already finalized, drop the block.
|
||||||
|
*
|
||||||
|
* If a slot is finalized, there's no point in considering any other blocks for that slot.
|
||||||
|
*
|
||||||
|
* TODO: We can more strongly throw away blocks based on the `last_finalized_block` related
|
||||||
|
* to this `last_finalized_slot`. Namely, any block in a future slot must include the
|
||||||
|
* `last_finalized_block` in it's chain.
|
||||||
|
*/
|
||||||
|
if block_slot <= self.last_finalized_slot {
|
||||||
|
return Err(SszBeaconBlockValidationError::SlotAlreadyFinalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the PoW chain hash is not known to us, drop it.
|
||||||
|
*
|
||||||
|
* We only accept blocks that reference a known PoW hash.
|
||||||
|
*
|
||||||
|
* Note: it is not clear what a "known" PoW chain ref is. Likely it means the block hash is
|
||||||
|
* "sufficienty deep in the canonical PoW chain". This should be clarified as the spec
|
||||||
|
* crystallizes.
|
||||||
|
*/
|
||||||
|
let pow_chain_reference = b.pow_chain_reference();
|
||||||
|
if !self.pow_store.block_hash_exists(b.pow_chain_reference())? {
|
||||||
|
return Err(SszBeaconBlockValidationError::UnknownPoWChainRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Store a slice of the serialized attestations from the block SSZ.
|
||||||
|
*/
|
||||||
|
let attestations_ssz = &b.attestations_without_length();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get a slice of the first serialized attestation (the 0'th) and decode it into
|
||||||
|
* a full AttestationRecord object.
|
||||||
|
*
|
||||||
|
* The first attestation must be validated separately as it must contain a signature of the
|
||||||
|
* proposer of the previous block (this is checked later in this function).
|
||||||
|
*/
|
||||||
|
let (first_attestation_ssz, next_index) = split_one_attestation(&attestations_ssz, 0)?;
|
||||||
|
let (first_attestation, _) = AttestationRecord::ssz_decode(&first_attestation_ssz, 0)?;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The first attestation may not have oblique hashes.
|
||||||
|
*
|
||||||
|
* The presence of oblique hashes in the first attestation would indicate that the proposer
|
||||||
|
* of the previous block is attesting to some other block than the one they produced.
|
||||||
|
*/
|
||||||
|
if !first_attestation.oblique_parent_hashes.is_empty() {
|
||||||
|
return Err(SszBeaconBlockValidationError::ProposerAttestationHasObliqueHashes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read the parent hash from the block we are validating then attempt to load
|
||||||
|
* that parent block ssz from the database.
|
||||||
|
*
|
||||||
|
* If that parent doesn't exist in the database or is invalid, reject the block.
|
||||||
|
*
|
||||||
|
* Also, read the slot from the parent block for later use.
|
||||||
|
*/
|
||||||
|
let parent_hash = b
|
||||||
|
.parent_hash()
|
||||||
|
.ok_or(SszBeaconBlockValidationError::BadAncestorHashesSsz)?;
|
||||||
|
let parent_block_slot = match self.block_store.get_serialized_block(&parent_hash)? {
|
||||||
|
None => return Err(SszBeaconBlockValidationError::UnknownParentHash),
|
||||||
|
Some(ssz) => {
|
||||||
|
let parent_block = SszBeaconBlock::from_slice(&ssz[..])?;
|
||||||
|
parent_block.slot()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The parent block slot must be less than the block slot.
|
||||||
|
*
|
||||||
|
* In other words, the parent must come before the child.
|
||||||
|
*/
|
||||||
|
if parent_block_slot >= block_slot {
|
||||||
|
return Err(SszBeaconBlockValidationError::ParentSlotHigherThanBlockSlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generate the context in which attestations will be validated.
|
||||||
|
*/
|
||||||
|
let attestation_validation_context = Arc::new(AttestationValidationContext {
|
||||||
|
block_slot,
|
||||||
|
parent_block_slot,
|
||||||
|
cycle_length: self.cycle_length,
|
||||||
|
last_justified_slot: self.last_justified_slot,
|
||||||
|
recent_block_hashes: self.recent_block_hashes.clone(),
|
||||||
|
block_store: self.block_store.clone(),
|
||||||
|
validator_store: self.validator_store.clone(),
|
||||||
|
attester_map: self.attester_map.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Validate this first attestation.
|
||||||
|
*/
|
||||||
|
let attestation_voters =
|
||||||
|
attestation_validation_context.validate_attestation(&first_attestation)?;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Attempt to read load the parent block proposer from the proposer map. Return with an
|
||||||
|
* error if it fails.
|
||||||
|
*
|
||||||
|
* If the signature of proposer for the parent slot was not present in the first (0'th)
|
||||||
|
* attestation of this block, reject the block.
|
||||||
|
*/
|
||||||
|
let parent_block_proposer = self
|
||||||
|
.proposer_map
|
||||||
|
.get(&parent_block_slot)
|
||||||
|
.ok_or(SszBeaconBlockValidationError::BadProposerMap)?;
|
||||||
|
if !attestation_voters.contains(&parent_block_proposer) {
|
||||||
|
return Err(SszBeaconBlockValidationError::NoProposerSignature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Split the remaining attestations into a vector of slices, each containing
|
||||||
|
* a single serialized attestation record.
|
||||||
|
*/
|
||||||
|
let other_attestations = split_all_attestations(attestations_ssz, next_index)?;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Verify each other AttestationRecord.
|
||||||
|
*
|
||||||
|
* This uses the `rayon` library to do "sometimes" parallelization. Put simply,
|
||||||
|
* if there are some spare threads, the verification of attestation records will happen
|
||||||
|
* concurrently.
|
||||||
|
*
|
||||||
|
* There is a thread-safe `failure` variable which is set whenever an attestation fails
|
||||||
|
* validation. This is so all attestation validation is halted if a single bad attestation
|
||||||
|
* is found.
|
||||||
|
*/
|
||||||
|
let failure: RwLock<Option<SszBeaconBlockValidationError>> = RwLock::new(None);
|
||||||
|
let mut deserialized_attestations: Vec<AttestationRecord> = other_attestations
|
||||||
|
.par_iter()
|
||||||
|
.filter_map(|attestation_ssz| {
|
||||||
|
/*
|
||||||
|
* If some thread has set the `failure` variable to `Some(error)` the abandon
|
||||||
|
* attestation serialization and validation. Also, fail early if the lock has been
|
||||||
|
* poisoned.
|
||||||
|
*/
|
||||||
|
match failure.read() {
|
||||||
|
Ok(ref option) if option.is_none() => (),
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* If there has not been a failure yet, attempt to serialize and validate the
|
||||||
|
* attestation.
|
||||||
|
*/
|
||||||
|
match AttestationRecord::ssz_decode(&attestation_ssz, 0) {
|
||||||
|
/*
|
||||||
|
* Deserialization failed, therefore the block is invalid.
|
||||||
|
*/
|
||||||
|
Err(e) => {
|
||||||
|
/*
|
||||||
|
* If the failure lock isn't poisoned, set it to some error.
|
||||||
|
*/
|
||||||
|
if let Ok(mut f) = failure.write() {
|
||||||
|
*f = Some(SszBeaconBlockValidationError::from(e));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Deserialization succeeded and the attestation should be validated.
|
||||||
|
*/
|
||||||
|
Ok((attestation, _)) => {
|
||||||
|
match attestation_validation_context.validate_attestation(&attestation) {
|
||||||
|
/*
|
||||||
|
* Attestation validation failed with some error.
|
||||||
|
*/
|
||||||
|
Err(e) => {
|
||||||
|
/*
|
||||||
|
* If the failure lock isn't poisoned, set it to some error.
|
||||||
|
*/
|
||||||
|
if let Ok(mut f) = failure.write() {
|
||||||
|
*f = Some(SszBeaconBlockValidationError::from(e));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Attestation validation succeded.
|
||||||
|
*/
|
||||||
|
Ok(_) => Some(attestation),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
match failure.into_inner() {
|
||||||
|
Err(_) => return Err(SszBeaconBlockValidationError::RwLockPoisoned),
|
||||||
|
Ok(failure) => match failure {
|
||||||
|
Some(error) => return Err(error),
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add the first attestation to the vec of deserialized attestations at
|
||||||
|
* index 0.
|
||||||
|
*/
|
||||||
|
deserialized_attestations.insert(0, first_attestation);
|
||||||
|
|
||||||
|
let (ancestor_hashes, _) = Decodable::ssz_decode(&b.ancestor_hashes(), 0)
|
||||||
|
.map_err(|_| SszBeaconBlockValidationError::BadAncestorHashesSsz)?;
|
||||||
|
let (specials, _) = Decodable::ssz_decode(&b.specials(), 0)
|
||||||
|
.map_err(|_| SszBeaconBlockValidationError::BadSpecialsSsz)?;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we have reached this point, the block is a new valid block that is worthy of
|
||||||
|
* processing.
|
||||||
|
*/
|
||||||
|
let block = BeaconBlock {
|
||||||
|
slot: block_slot,
|
||||||
|
randao_reveal: Hash256::from(b.randao_reveal()),
|
||||||
|
pow_chain_reference: Hash256::from(pow_chain_reference),
|
||||||
|
ancestor_hashes,
|
||||||
|
active_state_root: Hash256::from(b.act_state_root()),
|
||||||
|
crystallized_state_root: Hash256::from(b.cry_state_root()),
|
||||||
|
attestations: deserialized_attestations,
|
||||||
|
specials,
|
||||||
|
};
|
||||||
|
Ok(block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DBError> for SszBeaconBlockValidationError {
|
||||||
|
fn from(e: DBError) -> Self {
|
||||||
|
SszBeaconBlockValidationError::DBError(e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AttestationSplitError> for SszBeaconBlockValidationError {
|
||||||
|
fn from(e: AttestationSplitError) -> Self {
|
||||||
|
match e {
|
||||||
|
AttestationSplitError::TooShort => SszBeaconBlockValidationError::BadAttestationSsz,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SszBeaconBlockError> for SszBeaconBlockValidationError {
|
||||||
|
fn from(e: SszBeaconBlockError) -> Self {
|
||||||
|
match e {
|
||||||
|
SszBeaconBlockError::TooShort => {
|
||||||
|
SszBeaconBlockValidationError::DBError("Bad parent block in db.".to_string())
|
||||||
|
}
|
||||||
|
SszBeaconBlockError::TooLong => {
|
||||||
|
SszBeaconBlockValidationError::DBError("Bad parent block in db.".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DecodeError> for SszBeaconBlockValidationError {
|
||||||
|
fn from(e: DecodeError) -> Self {
|
||||||
|
match e {
|
||||||
|
DecodeError::TooShort => SszBeaconBlockValidationError::BadAttestationSsz,
|
||||||
|
DecodeError::TooLong => SszBeaconBlockValidationError::BadAttestationSsz,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AttestationValidationError> for SszBeaconBlockValidationError {
|
||||||
|
fn from(e: AttestationValidationError) -> Self {
|
||||||
|
SszBeaconBlockValidationError::AttestationValidationError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tests for block validation are contained in the root directory "tests" directory (AKA
|
||||||
|
* "integration tests directory").
|
||||||
|
*/
|
9
beacon_chain/block_validation/src/lib.rs
Normal file
9
beacon_chain/block_validation/src/lib.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
extern crate attestation_validation;
|
||||||
|
extern crate bls;
|
||||||
|
extern crate db;
|
||||||
|
extern crate hashing;
|
||||||
|
extern crate ssz;
|
||||||
|
extern crate ssz_helpers;
|
||||||
|
extern crate types;
|
||||||
|
|
||||||
|
pub mod block_validation;
|
@ -37,6 +37,12 @@ impl AttestationData {
|
|||||||
justified_block_hash: Hash256::zero(),
|
justified_block_hash: Hash256::zero(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Implement this as a merkle root, once tree_ssz is implemented.
|
||||||
|
// https://github.com/sigp/lighthouse/issues/92
|
||||||
|
pub fn canonical_root(&self) -> Hash256 {
|
||||||
|
Hash256::zero()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Encodable for AttestationData {
|
impl Encodable for AttestationData {
|
||||||
|
@ -2,6 +2,7 @@ use super::ValidatorRegistration;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct ChainConfig {
|
pub struct ChainConfig {
|
||||||
|
// Old, potentially outdated constants
|
||||||
pub cycle_length: u8,
|
pub cycle_length: u8,
|
||||||
pub deposit_size_gwei: u64,
|
pub deposit_size_gwei: u64,
|
||||||
pub shard_count: u16,
|
pub shard_count: u16,
|
||||||
@ -10,6 +11,10 @@ pub struct ChainConfig {
|
|||||||
pub genesis_time: u64,
|
pub genesis_time: u64,
|
||||||
pub slot_duration_millis: u64,
|
pub slot_duration_millis: u64,
|
||||||
pub initial_validators: Vec<ValidatorRegistration>,
|
pub initial_validators: Vec<ValidatorRegistration>,
|
||||||
|
|
||||||
|
// New constants
|
||||||
|
pub epoch_length: u64,
|
||||||
|
pub min_attestation_inclusion_delay: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -28,6 +33,10 @@ impl ChainConfig {
|
|||||||
genesis_time: TEST_GENESIS_TIME,
|
genesis_time: TEST_GENESIS_TIME,
|
||||||
slot_duration_millis: 16 * 1000,
|
slot_duration_millis: 16 * 1000,
|
||||||
initial_validators: vec![],
|
initial_validators: vec![],
|
||||||
|
|
||||||
|
// New
|
||||||
|
epoch_length: 64,
|
||||||
|
min_attestation_inclusion_delay: 4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,6 +63,10 @@ impl ChainConfig {
|
|||||||
genesis_time: TEST_GENESIS_TIME, // arbitrary
|
genesis_time: TEST_GENESIS_TIME, // arbitrary
|
||||||
slot_duration_millis: 16 * 1000,
|
slot_duration_millis: 16 * 1000,
|
||||||
initial_validators: vec![],
|
initial_validators: vec![],
|
||||||
|
|
||||||
|
// New constants
|
||||||
|
epoch_length: 64,
|
||||||
|
min_attestation_inclusion_delay: 4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,31 +2,16 @@ use super::Hash256;
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct CrosslinkRecord {
|
pub struct CrosslinkRecord {
|
||||||
pub recently_changed: bool,
|
|
||||||
pub slot: u64,
|
pub slot: u64,
|
||||||
pub hash: Hash256,
|
pub shard_block_hash: Hash256,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CrosslinkRecord {
|
impl CrosslinkRecord {
|
||||||
/// Generates a new instance where `dynasty` and `hash` are both zero.
|
/// Generates a new instance where `dynasty` and `hash` are both zero.
|
||||||
pub fn zero() -> Self {
|
pub fn zero() -> Self {
|
||||||
Self {
|
Self {
|
||||||
recently_changed: false,
|
|
||||||
slot: 0,
|
slot: 0,
|
||||||
hash: Hash256::zero(),
|
shard_block_hash: Hash256::zero(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_crosslink_record_zero() {
|
|
||||||
let c = CrosslinkRecord::zero();
|
|
||||||
assert_eq!(c.recently_changed, false);
|
|
||||||
assert_eq!(c.slot, 0);
|
|
||||||
assert!(c.hash.is_zero());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user