Refactor block val. into "BlockValidationContext"

This commit is contained in:
Paul Hauner 2018-09-29 16:07:59 +09:30
parent bc27be147f
commit 0b99951bf8
No known key found for this signature in database
GPG Key ID: 303E4494BB28068C
4 changed files with 252 additions and 245 deletions

View File

@ -5,7 +5,7 @@ use self::test::Bencher;
use std::sync::Arc; use std::sync::Arc;
use super::{ use super::{
validate_ssz_block, BlockValidationContext,
AttesterMap, AttesterMap,
ProposerMap, ProposerMap,
}; };
@ -50,17 +50,18 @@ fn bench_block_validation_scenario<F>(
let proposer_map = Arc::new(proposer_map); let proposer_map = Arc::new(proposer_map);
let attester_map = Arc::new(attester_map); let attester_map = Arc::new(attester_map);
b.iter(|| { b.iter(|| {
validate_ssz_block( let context = BlockValidationContext {
&ssz_block, present_slot: validation_slot,
validation_slot, cycle_length: params.cycle_length,
params.cycle_length, last_justified_slot: validation_last_justified_slot,
validation_last_justified_slot, parent_hashes: parent_hashes.clone(),
&parent_hashes.clone(), proposer_map: proposer_map.clone(),
&proposer_map.clone(), attester_map: attester_map.clone(),
&attester_map.clone(), block_store: stores.block.clone(),
&stores.block.clone(), validator_store: stores.validator.clone(),
&stores.validator.clone(), pow_store: stores.pow_chain.clone()
&stores.pow_chain.clone()) };
context.validate_ssz_block(&ssz_block)
}); });
} }

View File

@ -18,7 +18,7 @@ use super::common::maps::{
ProposerMap, ProposerMap,
}; };
pub use self::validate_ssz_block::{ pub use self::validate_ssz_block::{
validate_ssz_block, BlockValidationContext,
SszBlockValidationError, SszBlockValidationError,
BlockStatus, BlockStatus,
}; };

View File

@ -5,7 +5,7 @@ use self::ssz::{
}; };
use std::sync::Arc; use std::sync::Arc;
use super::{ use super::{
validate_ssz_block, BlockValidationContext,
SszBlockValidationError, SszBlockValidationError,
BlockStatus, BlockStatus,
AttesterMap, AttesterMap,
@ -101,21 +101,7 @@ pub fn setup_block_validation_scenario(params: &TestParams)
proposer_map.insert(block_slot, validator_index); proposer_map.insert(block_slot, validator_index);
proposer_map proposer_map
}; };
/*
let attestation_slot = block_slot - 1;
let (attester_map, attestations, _keypairs) =
generate_attestations_for_slot(
attestation_slot,
block_slot,
shards_per_slot,
validators_per_shard,
cycle_length,
&parent_hashes,
&Hash256::from("shard_hash".as_bytes()),
&justified_block_hash,
attestations_justified_slot,
&stores);
*/
let (attester_map, attestations, _keypairs) = { let (attester_map, attestations, _keypairs) = {
let mut i = 0; let mut i = 0;
let attestation_slot = block_slot - 1; let attestation_slot = block_slot - 1;
@ -244,17 +230,18 @@ pub fn run_block_validation_scenario<F>(
let ssz_block = SszBlock::from_slice(&ssz_bytes[..]) let ssz_block = SszBlock::from_slice(&ssz_bytes[..])
.unwrap(); .unwrap();
validate_ssz_block( let context = BlockValidationContext {
&ssz_block, present_slot: validation_slot,
validation_slot, cycle_length: params.cycle_length,
params.cycle_length, last_justified_slot: validation_last_justified_slot,
validation_last_justified_slot, parent_hashes: Arc::new(parent_hashes),
&Arc::new(parent_hashes), proposer_map: Arc::new(proposer_map),
&Arc::new(proposer_map), attester_map: Arc::new(attester_map),
&Arc::new(attester_map), block_store: stores.block.clone(),
&stores.block.clone(), validator_store: stores.validator.clone(),
&stores.validator.clone(), pow_store: stores.pow_chain.clone()
&stores.pow_chain.clone()) };
context.validate_ssz_block(&ssz_block)
} }
fn get_simple_params() -> TestParams { fn get_simple_params() -> TestParams {

View File

@ -60,237 +60,256 @@ pub enum SszBlockValidationError {
DatabaseError(String), DatabaseError(String),
} }
/// Validate some SszBlock. An SszBlock varies from a Block in that is a read-only structure /// The context against which a block should be validated.
/// that reads directly from encoded SSZ. pub struct BlockValidationContext<T>
///
/// The reason to validate an SzzBlock 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.
///
/// Of course, this function will only be more efficient if a block is already serialized.
/// Serializing a complete block and then validating with this function will be less
/// efficient than just validating the original block.
///
/// 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<T>(b: &SszBlock,
expected_slot: u64,
cycle_length: u8,
last_justified_slot: u64,
parent_hashes: &Arc<Vec<Hash256>>,
proposer_map: &Arc<ProposerMap>,
attester_map: &Arc<AttesterMap>,
block_store: &Arc<BlockStore<T>>,
validator_store: &Arc<ValidatorStore<T>>,
pow_store: &Arc<PoWChainStore<T>>)
-> Result<(BlockStatus, Option<Block>), SszBlockValidationError>
where T: ClientDB + Sized 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,
/// A vec of the hashes of the blocks preceeding the present slot.
pub parent_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<BlockStore<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> BlockValidationContext<T>
* If the block slot corresponds to a slot in the future (according to the local time), where T: ClientDB
* drop it. {
*/ /// Validate some SszBlock against a block validation context. An SszBlock varies from a Block in
let block_slot = b.slot_number(); /// that is a read-only structure that reads directly from encoded SSZ.
if block_slot > expected_slot { ///
return Err(SszBlockValidationError::FutureSlot); /// The reason to validate an SzzBlock 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.
///
/// Of course, this function will only be more efficient if a block is already serialized.
/// Serializing a complete block and then validating with this function will be less
/// efficient than just validating the original block.
///
/// 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: &SszBlock)
-> Result<(BlockStatus, Option<Block>), SszBlockValidationError>
where T: ClientDB + Sized
{
/* /*
* If this block is already known, return immediately. * If the block slot corresponds to a slot in the future (according to the local time),
*/ * drop it.
let block_hash = &b.block_hash(); */
if block_store.block_exists(&block_hash)? { let block_slot = b.slot_number();
return Ok((BlockStatus::KnownBlock, None)); if block_slot > self.present_slot {
} return Err(SszBlockValidationError::FutureSlot);
}
/*
* If this block is already known, return immediately.
*/
let block_hash = &b.block_hash();
if self.block_store.block_exists(&block_hash)? {
return Ok((BlockStatus::KnownBlock, None));
}
/* /*
* If the PoW chain hash is not known to us, drop it. * If the PoW chain hash is not known to us, drop it.
* *
* We only accept blocks that reference a known PoW hash. * We only accept blocks that reference a known PoW hash.
* *
* Note: it is not clear what a "known" PoW chain ref is. Likely, * Note: it is not clear what a "known" PoW chain ref is. Likely,
* it means "sufficienty deep in the canonical PoW chain". * it means "sufficienty deep in the canonical PoW chain".
*/ */
let pow_chain_ref = b.pow_chain_ref(); let pow_chain_ref = b.pow_chain_ref();
if !pow_store.block_hash_exists(b.pow_chain_ref())? { if !self.pow_store.block_hash_exists(b.pow_chain_ref())? {
return Err(SszBlockValidationError::UnknownPoWChainRef); return Err(SszBlockValidationError::UnknownPoWChainRef);
} }
/* /*
* Store a reference to the serialized attestations from the block. * Store a reference to the serialized attestations from the block.
*/ */
let attestations_ssz = &b.attestations(); let attestations_ssz = &b.attestations();
/* /*
* Get a slice of the first serialized attestation (the 0th) and decode it into * Get a slice of the first serialized attestation (the 0th) and decode it into
* a full AttestationRecord object. * a full AttestationRecord object.
*/ */
let (first_attestation_ssz, next_index) = split_one_attestation( let (first_attestation_ssz, next_index) = split_one_attestation(
&attestations_ssz, &attestations_ssz,
0)?; 0)?;
let (first_attestation, _) = AttestationRecord::ssz_decode( let (first_attestation, _) = AttestationRecord::ssz_decode(
&first_attestation_ssz, 0)?; &first_attestation_ssz, 0)?;
/* /*
* Validate this first attestation. * Validate this first attestation.
* *
* It is a requirement that the block proposer for this slot * It is a requirement that the block proposer for this slot
* must have signed the 0th attestation record. * must have signed the 0th attestation record.
*/ */
let attestation_voters = validate_attestation( let attestation_voters = validate_attestation(
&first_attestation, &first_attestation,
block_slot, block_slot,
cycle_length, self.cycle_length,
last_justified_slot, self.last_justified_slot,
&parent_hashes, &self.parent_hashes,
&block_store, &self.block_store,
&validator_store, &self.validator_store,
&attester_map)?; &self.attester_map)?;
/* /*
* If the set of voters is None, the attestation was invalid. * If the set of voters is None, the attestation was invalid.
*/ */
let attestation_voters = attestation_voters let attestation_voters = attestation_voters
.ok_or(SszBlockValidationError:: .ok_or(SszBlockValidationError::
FirstAttestationSignatureFailed)?; FirstAttestationSignatureFailed)?;
/* /*
* Read the proposer from the map of slot -> validator index. * Read the proposer from the map of slot -> validator index.
*/ */
let proposer = proposer_map.get(&block_slot) let proposer = self.proposer_map.get(&block_slot)
.ok_or(SszBlockValidationError::BadProposerMap)?; .ok_or(SszBlockValidationError::BadProposerMap)?;
/* /*
* If the proposer for this slot was not a voter, reject the block. * If the proposer for this slot was not a voter, reject the block.
*/ */
if !attestation_voters.contains(&proposer) { if !attestation_voters.contains(&proposer) {
return Err(SszBlockValidationError::NoProposerSignature); return Err(SszBlockValidationError::NoProposerSignature);
} }
/* /*
* Split the remaining attestations into a vector of slices, each containing * Split the remaining attestations into a vector of slices, each containing
* a single serialized attestation record. * a single serialized attestation record.
*/ */
let other_attestations = split_all_attestations(attestations_ssz, let other_attestations = split_all_attestations(attestations_ssz,
next_index)?; next_index)?;
/* /*
* Verify each other AttestationRecord. * Verify each other AttestationRecord.
* *
* This uses the `rayon` library to do "sometimes" parallelization. Put simply, * This uses the `rayon` library to do "sometimes" parallelization. Put simply,
* if there are some spare threads, the verification of attestation records will happen * if there are some spare threads, the verification of attestation records will happen
* concurrently. * concurrently.
* *
* There is a thread-safe `failure` variable which is set whenever an attestation fails * 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 * validation. This is so all attestation validation is halted if a single bad attestation
* is found. * is found.
*/ */
let failure: RwLock<Option<SszBlockValidationError>> = RwLock::new(None); let failure: RwLock<Option<SszBlockValidationError>> = RwLock::new(None);
let deserialized_attestations: Vec<AttestationRecord> = other_attestations let deserialized_attestations: Vec<AttestationRecord> = other_attestations
.par_iter() .par_iter()
.filter_map(|attestation_ssz| { .filter_map(|attestation_ssz| {
/*
* If some thread has set the `failure` variable to `Some(error)` the abandon
* attestation serialization and validation.
*/
if let Some(_) = *failure.read().unwrap() {
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. * If some thread has set the `failure` variable to `Some(error)` the abandon
* attestation serialization and validation.
*/ */
Err(e) => { if let Some(_) = *failure.read().unwrap() {
let mut failure = failure.write().unwrap(); return None;
*failure = Some(SszBlockValidationError::from(e));
None
} }
/* /*
* Deserialization succeeded and the attestation should be validated. * If there has not been a failure yet, attempt to serialize and validate the
* attestation.
*/ */
Ok((attestation, _)) => { match AttestationRecord::ssz_decode(&attestation_ssz, 0) {
let result = validate_attestation( /*
&attestation, * Deserialization failed, therefore the block is invalid.
block_slot, */
cycle_length, Err(e) => {
last_justified_slot, let mut failure = failure.write().unwrap();
&parent_hashes, *failure = Some(SszBlockValidationError::from(e));
&block_store, None
&validator_store, }
&attester_map); /*
match result { * Deserialization succeeded and the attestation should be validated.
/* */
* Attestation validation failed with some error. Ok((attestation, _)) => {
*/ let result = validate_attestation(
Err(e) => { &attestation,
let mut failure = failure.write().unwrap(); block_slot,
*failure = Some(SszBlockValidationError::from(e)); self.cycle_length,
None self.last_justified_slot,
&self.parent_hashes,
&self.block_store,
&self.validator_store,
&self.attester_map);
match result {
/*
* Attestation validation failed with some error.
*/
Err(e) => {
let mut failure = failure.write().unwrap();
*failure = Some(SszBlockValidationError::from(e));
None
}
/*
* Attestation validation failed due to a bad signature.
*/
Ok(None) => {
let mut failure = failure.write().unwrap();
*failure = Some(SszBlockValidationError::AttestationSignatureFailed);
None
}
/*
* Attestation validation succeded.
*/
Ok(_) => Some(attestation)
} }
/*
* Attestation validation failed due to a bad signature.
*/
Ok(None) => {
let mut failure = failure.write().unwrap();
*failure = Some(SszBlockValidationError::AttestationSignatureFailed);
None
}
/*
* Attestation validation succeded.
*/
Ok(_) => Some(attestation)
} }
} }
} })
}) .collect();
.collect();
match failure.into_inner() { match failure.into_inner() {
Err(_) => return Err(SszBlockValidationError::RwLockPoisoned), Err(_) => return Err(SszBlockValidationError::RwLockPoisoned),
Ok(failure) => { Ok(failure) => {
match failure { match failure {
Some(error) => return Err(error), Some(error) => return Err(error),
_ => () _ => ()
} }
}
} }
/*
* If we have reached this point, the block is a new valid block that is worthy of
* processing.
*/
/*
* If the block's parent_hash _is_ in the canonical chain, the block is a
* new block in the canonical chain. Otherwise, it's a new block in a fork chain.
*/
let parent_hash = b.parent_hash();
let status = if self.block_store.block_exists_in_canonical_chain(&parent_hash)? {
BlockStatus::NewBlockInCanonicalChain
} else {
BlockStatus::NewBlockInForkChain
};
let block = Block {
parent_hash: Hash256::from(parent_hash),
slot_number: block_slot,
randao_reveal: Hash256::from(b.randao_reveal()),
attestations: deserialized_attestations,
pow_chain_ref: Hash256::from(pow_chain_ref),
active_state_root: Hash256::from(b.act_state_root()),
crystallized_state_root: Hash256::from(b.cry_state_root()),
};
Ok((status, Some(block)))
} }
/*
* If we have reached this point, the block is a new valid block that is worthy of
* processing.
*/
/*
* If the block's parent_hash _is_ in the canonical chain, the block is a
* new block in the canonical chain. Otherwise, it's a new block in a fork chain.
*/
let parent_hash = b.parent_hash();
let status = if block_store.block_exists_in_canonical_chain(&parent_hash)? {
BlockStatus::NewBlockInCanonicalChain
} else {
BlockStatus::NewBlockInForkChain
};
let block = Block {
parent_hash: Hash256::from(parent_hash),
slot_number: block_slot,
randao_reveal: Hash256::from(b.randao_reveal()),
attestations: deserialized_attestations,
pow_chain_ref: Hash256::from(pow_chain_ref),
active_state_root: Hash256::from(b.act_state_root()),
crystallized_state_root: Hash256::from(b.cry_state_root()),
};
Ok((status, Some(block)))
} }
impl From<DBError> for SszBlockValidationError { impl From<DBError> for SszBlockValidationError {